File indexing completed on 2024-03-24 03:47:39
0001 /* 0002 SPDX-FileCopyrightText: 2004-2020 Jeff Woods <jcwoods@bellsouth.net> 0003 SPDX-FileCopyrightText: 2004-2020 Jason Harris <jharris@30doradus.org> 0004 SPDX-FileCopyrightText: Prakash Mohan <prakash.mohan@kdemail.net> 0005 SPDX-FileCopyrightText: Akarsh Simha <akarsh@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "observinglist.h" 0011 0012 #include "config-kstars.h" 0013 0014 #include "constellationboundarylines.h" 0015 #include "fov.h" 0016 #include "imageviewer.h" 0017 #include "ksalmanac.h" 0018 #include "ksnotification.h" 0019 #include "ksdssdownloader.h" 0020 #include "kspaths.h" 0021 #include "kstars.h" 0022 #include "kstarsdata.h" 0023 #include "ksutils.h" 0024 #include "obslistpopupmenu.h" 0025 #include "obslistwizard.h" 0026 #include "Options.h" 0027 #include "sessionsortfilterproxymodel.h" 0028 #include "skymap.h" 0029 #include "thumbnailpicker.h" 0030 #include "dialogs/detaildialog.h" 0031 #include "dialogs/finddialog.h" 0032 #include "dialogs/locationdialog.h" 0033 #include "oal/execute.h" 0034 #include "skycomponents/skymapcomposite.h" 0035 #include "skyobjects/skyobject.h" 0036 #include "skyobjects/starobject.h" 0037 #include "tools/altvstime.h" 0038 #include "tools/eyepiecefield.h" 0039 #include "tools/wutdialog.h" 0040 0041 #ifdef HAVE_INDI 0042 #include <basedevice.h> 0043 #include "indi/indilistener.h" 0044 #include "indi/drivermanager.h" 0045 #include "indi/driverinfo.h" 0046 #include "ekos/manager.h" 0047 #endif 0048 0049 #include <KPlotting/KPlotAxis> 0050 #include <KPlotting/KPlotObject> 0051 #include <KMessageBox> 0052 #include <QMessageBox> 0053 0054 #include <kstars_debug.h> 0055 0056 // 0057 // ObservingListUI 0058 // --------------------------------- 0059 ObservingListUI::ObservingListUI(QWidget *p) : QFrame(p) 0060 { 0061 setupUi(this); 0062 } 0063 0064 // 0065 // ObservingList 0066 // --------------------------------- 0067 ObservingList::ObservingList() 0068 : QDialog((QWidget *)KStars::Instance()), LogObject(nullptr), m_CurrentObject(nullptr), isModified(false), m_dl(nullptr), 0069 m_manager{ CatalogsDB::dso_db_path() } 0070 { 0071 #ifdef Q_OS_OSX 0072 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0073 #endif 0074 ui = new ObservingListUI(this); 0075 QVBoxLayout *mainLayout = new QVBoxLayout; 0076 mainLayout->addWidget(ui); 0077 setWindowTitle(i18nc("@title:window", "Observation Planner")); 0078 0079 setLayout(mainLayout); 0080 0081 dt = KStarsDateTime::currentDateTime(); 0082 setFocusPolicy(Qt::StrongFocus); 0083 geo = KStarsData::Instance()->geo(); 0084 sessionView = false; 0085 m_listFileName = QString(); 0086 pmenu.reset(new ObsListPopupMenu()); 0087 //Set up the Table Views 0088 m_WishListModel.reset(new QStandardItemModel(0, 5, this)); 0089 m_SessionModel.reset(new QStandardItemModel(0, 5)); 0090 0091 m_WishListModel->setHorizontalHeaderLabels( 0092 QStringList() << i18n("Name") << i18n("Alternate Name") << i18nc("Right Ascension", "RA (J2000)") 0093 << i18nc("Declination", "Dec (J2000)") << i18nc("Magnitude", "Mag") << i18n("Type") 0094 << i18n("Current Altitude")); 0095 m_SessionModel->setHorizontalHeaderLabels( 0096 QStringList() << i18n("Name") << i18n("Alternate Name") << i18nc("Right Ascension", "RA (J2000)") 0097 << i18nc("Declination", "Dec (J2000)") << i18nc("Magnitude", "Mag") << i18n("Type") 0098 << i18nc("Constellation", "Constell.") << i18n("Time") << i18nc("Altitude", "Alt") 0099 << i18nc("Azimuth", "Az")); 0100 0101 m_WishListSortModel.reset(new QSortFilterProxyModel(this)); 0102 m_WishListSortModel->setSourceModel(m_WishListModel.get()); 0103 m_WishListSortModel->setDynamicSortFilter(true); 0104 m_WishListSortModel->setSortRole(Qt::UserRole); 0105 ui->WishListView->setModel(m_WishListSortModel.get()); 0106 ui->WishListView->horizontalHeader()->setStretchLastSection(true); 0107 0108 ui->WishListView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); 0109 m_SessionSortModel.reset(new SessionSortFilterProxyModel()); 0110 m_SessionSortModel->setSourceModel(m_SessionModel.get()); 0111 m_SessionSortModel->setDynamicSortFilter(true); 0112 ui->SessionView->setModel(m_SessionSortModel.get()); 0113 ui->SessionView->horizontalHeader()->setStretchLastSection(true); 0114 ui->SessionView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); 0115 ksal.reset(new KSAlmanac); 0116 ksal->setLocation(geo); 0117 ui->avt->setGeoLocation(geo); 0118 ui->avt->setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet()); 0119 ui->avt->setLimits(-12.0, 12.0, -90.0, 90.0); 0120 ui->avt->axis(KPlotWidget::BottomAxis)->setTickLabelFormat('t'); 0121 ui->avt->axis(KPlotWidget::BottomAxis)->setLabel(i18n("Local Time")); 0122 ui->avt->axis(KPlotWidget::TopAxis)->setTickLabelFormat('t'); 0123 ui->avt->axis(KPlotWidget::TopAxis)->setTickLabelsShown(true); 0124 ui->DateEdit->setDate(dt.date()); 0125 ui->SetLocation->setText(geo->fullName()); 0126 ui->ImagePreview->installEventFilter(this); 0127 ui->WishListView->viewport()->installEventFilter(this); 0128 ui->WishListView->installEventFilter(this); 0129 ui->SessionView->viewport()->installEventFilter(this); 0130 ui->SessionView->installEventFilter(this); 0131 // setDefaultImage(); 0132 //Connections 0133 connect(ui->WishListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotCenterObject())); 0134 connect(ui->WishListView->selectionModel(), 0135 SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(slotNewSelection())); 0136 connect(ui->SessionView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), 0137 this, SLOT(slotNewSelection())); 0138 connect(ui->WUTButton, SIGNAL(clicked()), this, SLOT(slotWUT())); 0139 connect(ui->FindButton, SIGNAL(clicked()), this, SLOT(slotFind())); 0140 connect(ui->OpenButton, SIGNAL(clicked()), this, SLOT(slotOpenList())); 0141 connect(ui->SaveButton, SIGNAL(clicked()), this, SLOT(slotSaveSession())); 0142 connect(ui->SaveAsButton, SIGNAL(clicked()), this, SLOT(slotSaveSessionAs())); 0143 connect(ui->WizardButton, SIGNAL(clicked()), this, SLOT(slotWizard())); 0144 connect(ui->batchAddButton, SIGNAL(clicked()), this, SLOT(slotBatchAdd())); 0145 connect(ui->SetLocation, SIGNAL(clicked()), this, SLOT(slotLocation())); 0146 connect(ui->Update, SIGNAL(clicked()), this, SLOT(slotUpdate())); 0147 connect(ui->DeleteImage, SIGNAL(clicked()), this, SLOT(slotDeleteCurrentImage())); 0148 connect(ui->SearchImage, SIGNAL(clicked()), this, SLOT(slotSearchImage())); 0149 connect(ui->SetTime, SIGNAL(clicked()), this, SLOT(slotSetTime())); 0150 connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotChangeTab(int))); 0151 connect(ui->saveImages, SIGNAL(clicked()), this, SLOT(slotSaveAllImages())); 0152 connect(ui->DeleteAllImages, SIGNAL(clicked()), this, SLOT(slotDeleteAllImages())); 0153 connect(ui->OALExport, SIGNAL(clicked()), this, SLOT(slotOALExport())); 0154 connect(ui->clearListB, SIGNAL(clicked()), this, SLOT(slotClearList())); 0155 //Add icons to Push Buttons 0156 ui->OpenButton->setIcon(QIcon::fromTheme("document-open")); 0157 ui->OpenButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0158 ui->SaveButton->setIcon(QIcon::fromTheme("document-save")); 0159 ui->SaveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0160 ui->SaveAsButton->setIcon( 0161 QIcon::fromTheme("document-save-as")); 0162 ui->SaveAsButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0163 ui->WizardButton->setIcon(QIcon::fromTheme("tools-wizard")); 0164 ui->WizardButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0165 noSelection = true; 0166 showScope = false; 0167 ui->NotesEdit->setEnabled(false); 0168 ui->SetTime->setEnabled(false); 0169 ui->TimeEdit->setEnabled(false); 0170 ui->SearchImage->setEnabled(false); 0171 ui->saveImages->setEnabled(false); 0172 ui->DeleteImage->setEnabled(false); 0173 ui->OALExport->setEnabled(false); 0174 0175 m_NoImagePixmap = 0176 QPixmap(":/images/noimage.png") 0177 .scaled(ui->ImagePreview->width(), ui->ImagePreview->height(), Qt::KeepAspectRatio, Qt::FastTransformation); 0178 m_altCostHelper = [this](const SkyPoint & p) -> QStandardItem * 0179 { 0180 const double inf = std::numeric_limits<double>::infinity(); 0181 double altCost = 0.; 0182 QString itemText; 0183 double maxAlt = p.maxAlt(*(geo->lat())); 0184 if (Options::obsListDemoteHole() && maxAlt > 90. - Options::obsListHoleSize()) 0185 maxAlt = 90. - Options::obsListHoleSize(); 0186 if (maxAlt <= 0.) 0187 { 0188 altCost = -inf; 0189 itemText = i18n("Never rises"); 0190 } 0191 else 0192 { 0193 altCost = (p.alt().Degrees() / maxAlt) * 100.; 0194 if (altCost < 0) 0195 itemText = i18nc("Short text to describe that object has not risen yet", "Not risen"); 0196 else 0197 { 0198 if (altCost > 100.) 0199 { 0200 altCost = -inf; 0201 itemText = i18nc("Object is in the Dobsonian hole", "In hole"); 0202 } 0203 else 0204 itemText = QString::number(altCost, 'f', 0) + '%'; 0205 } 0206 } 0207 0208 QStandardItem *altItem = new QStandardItem(itemText); 0209 altItem->setData(altCost, Qt::UserRole); 0210 // qCDebug(KSTARS) << "Updating altitude for " << p.ra().toHMSString() << " " << p.dec().toDMSString() << " alt = " << p.alt().toDMSString() << " info to " << itemText; 0211 return altItem; 0212 }; 0213 0214 // Needed to fix weird bug on Windows that started with Qt 5.9 that makes the title bar 0215 // not visible and therefore dialog not movable. 0216 #ifdef Q_OS_WIN 0217 move(100, 100); 0218 #endif 0219 } 0220 0221 void ObservingList::showEvent(QShowEvent *) 0222 { 0223 // ONLY run for first ever load 0224 0225 if (m_initialWishlistLoad == false) 0226 { 0227 m_initialWishlistLoad = true; 0228 0229 slotLoadWishList(); //Load the wishlist from disk if present 0230 m_CurrentObject = nullptr; 0231 setSaveImagesButton(); 0232 0233 slotUpdateAltitudes(); 0234 m_altitudeUpdater = new QTimer(this); 0235 connect(m_altitudeUpdater, SIGNAL(timeout()), this, SLOT(slotUpdateAltitudes())); 0236 m_altitudeUpdater->start(120000); // update altitudes every 2 minutes 0237 } 0238 } 0239 0240 //SLOTS 0241 0242 void ObservingList::slotAddObject(const SkyObject *_obj, bool session, bool update) 0243 { 0244 if (!m_initialWishlistLoad) 0245 { 0246 showEvent(nullptr); // Initialize the observing wishlist 0247 } 0248 bool addToWishList = true; 0249 if (!_obj) 0250 _obj = SkyMap::Instance()->clickedObject(); // Eh? Why? Weird default behavior. 0251 0252 if (!_obj) 0253 { 0254 qCWarning(KSTARS) << "Trying to add null object to observing list! Ignoring."; 0255 return; 0256 } 0257 0258 QString finalObjectName = getObjectName(_obj); 0259 0260 if (finalObjectName.isEmpty()) 0261 { 0262 KSNotification::sorry(i18n("Stars and objects whose names KStars does not know are not supported in the observing lists")); 0263 return; 0264 } 0265 0266 //First, make sure object is not already in the list 0267 QSharedPointer<SkyObject> obj = findObject(_obj); 0268 if (obj) 0269 { 0270 addToWishList = false; 0271 if (!session) 0272 { 0273 KStars::Instance()->statusBar()->showMessage( 0274 i18n("%1 is already in your wishlist.", finalObjectName), 0275 0); // FIXME: This message is too inconspicuous if using the Find dialog to add 0276 return; 0277 } 0278 } 0279 else 0280 { 0281 assert(!findObject(_obj, session)); 0282 qCDebug(KSTARS) << "Cloned object " << finalObjectName << " to add to observing list."; 0283 obj = QSharedPointer<SkyObject>( 0284 _obj->clone()); // Use a clone in case the original SkyObject is deleted due to change in catalog configuration. 0285 } 0286 0287 if (session && sessionList().contains(obj)) 0288 { 0289 KStars::Instance()->statusBar()->showMessage(i18n("%1 is already in the session plan.", finalObjectName), 0); 0290 return; 0291 } 0292 0293 // JM: If we are loading observing list from disk, solar system objects magnitudes are not calculated until later 0294 // Therefore, we manual invoke updateCoords to force computation of magnitude. 0295 if ((obj->type() == SkyObject::COMET || obj->type() == SkyObject::ASTEROID || obj->type() == SkyObject::MOON || 0296 obj->type() == SkyObject::PLANET) && 0297 obj->mag() == 0) 0298 { 0299 KSNumbers num(dt.djd()); 0300 CachingDms LST = geo->GSTtoLST(dt.gst()); 0301 obj->updateCoords(&num, true, geo->lat(), &LST, true); 0302 } 0303 0304 QString smag = "--"; 0305 if (-30.0 < obj->mag() && obj->mag() < 90.0) 0306 smag = QString::number(obj->mag(), 'f', 2); // The lower limit to avoid display of unrealistic comet magnitudes 0307 0308 SkyPoint p = obj->recomputeHorizontalCoords(dt, geo); 0309 0310 QList<QStandardItem *> itemList; 0311 0312 auto getItemWithUserRole = [](const QString & itemText) -> QStandardItem * 0313 { 0314 QStandardItem *ret = new QStandardItem(itemText); 0315 ret->setData(itemText, Qt::UserRole); 0316 return ret; 0317 }; 0318 0319 // Fill itemlist with items that are common to both wishlist additions and session plan additions 0320 auto populateItemList = [&getItemWithUserRole, &itemList, &finalObjectName, obj, &p, &smag]() 0321 { 0322 itemList.clear(); 0323 QStandardItem *keyItem = getItemWithUserRole(finalObjectName); 0324 keyItem->setData(QVariant::fromValue<void *>(static_cast<void *>(obj.data())), Qt::UserRole + 1); 0325 itemList 0326 << keyItem // NOTE: The rest of the methods assume that the SkyObject pointer is available in the first column! 0327 << getItemWithUserRole(obj->translatedLongName()) << getItemWithUserRole(p.ra0().toHMSString()) 0328 << getItemWithUserRole(p.dec0().toDMSString()) << getItemWithUserRole(smag) 0329 << getItemWithUserRole(obj->typeName()); 0330 }; 0331 0332 //Insert object in the Wish List 0333 if (addToWishList) 0334 { 0335 m_WishList.append(obj); 0336 m_CurrentObject = obj.data(); 0337 0338 //QString ra, dec; 0339 //ra = "";//p.ra().toHMSString(); 0340 //dec = p.dec().toDMSString(); 0341 0342 populateItemList(); 0343 // FIXME: Instead sort by a "clever" observability score, calculated as follows: 0344 // - First sort by (max altitude) - (current altitude) rounded off to the nearest 0345 // - Weight by declination - latitude (in the northern hemisphere, southern objects get higher precedence) 0346 // - Demote objects in the hole 0347 SkyPoint p = obj->recomputeHorizontalCoords(KStarsDateTime::currentDateTimeUtc(), geo); // Current => now 0348 itemList << m_altCostHelper(p); 0349 m_WishListModel->appendRow(itemList); 0350 0351 //Note addition in statusbar 0352 KStars::Instance()->statusBar()->showMessage(i18n("Added %1 to observing list.", finalObjectName), 0); 0353 ui->WishListView->resizeColumnsToContents(); 0354 if (!update) 0355 slotSaveList(); 0356 } 0357 //Insert object in the Session List 0358 if (session) 0359 { 0360 m_SessionList.append(obj); 0361 dt.setTime(TimeHash.value(finalObjectName, obj->transitTime(dt, geo))); 0362 dms lst(geo->GSTtoLST(dt.gst())); 0363 p.EquatorialToHorizontal(&lst, geo->lat()); 0364 0365 QString alt = "--", az = "--"; 0366 0367 QStandardItem *BestTime = new QStandardItem(); 0368 /* QString ra, dec; 0369 if(obj->name() == "star" ) { 0370 ra = obj->ra0().toHMSString(); 0371 dec = obj->dec0().toDMSString(); 0372 BestTime->setData( QString( "--" ), Qt::DisplayRole ); 0373 } 0374 else {*/ 0375 BestTime->setData(TimeHash.value(finalObjectName, obj->transitTime(dt, geo)), Qt::DisplayRole); 0376 alt = p.alt().toDMSString(); 0377 az = p.az().toDMSString(); 0378 //} 0379 // TODO: Change the rest of the parameters to their appropriate datatypes. 0380 populateItemList(); 0381 itemList << getItemWithUserRole(KSUtils::constNameToAbbrev( 0382 KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj.data()))) 0383 << BestTime << getItemWithUserRole(alt) << getItemWithUserRole(az); 0384 0385 m_SessionModel->appendRow(itemList); 0386 //Adding an object should trigger the modified flag 0387 isModified = true; 0388 ui->SessionView->resizeColumnsToContents(); 0389 //Note addition in statusbar 0390 KStars::Instance()->statusBar()->showMessage(i18n("Added %1 to session list.", finalObjectName), 0); 0391 SkyMap::Instance()->forceUpdate(); 0392 } 0393 setSaveImagesButton(); 0394 } 0395 0396 void ObservingList::slotRemoveObject(const SkyObject *_o, bool session, bool update) 0397 { 0398 if (!update) // EH?! 0399 { 0400 if (!_o) 0401 _o = SkyMap::Instance()->clickedObject(); 0402 else if (sessionView) //else if is needed as clickedObject should not be removed from the session list. 0403 session = true; 0404 } 0405 0406 // Is the pointer supplied in our own lists? 0407 const QList<QSharedPointer<SkyObject>> &list = (session ? sessionList() : obsList()); 0408 QStandardItemModel *currentModel = (session ? m_SessionModel.get() : m_WishListModel.get()); 0409 0410 QSharedPointer<SkyObject> o = findObject(_o, session); 0411 if (!o) 0412 { 0413 qWarning() << "Object (name: " << getObjectName(o.data()) 0414 << ") supplied to ObservingList::slotRemoveObject() was not found in the " 0415 << QString(session ? "session" : "observing") << " list!"; 0416 return; 0417 } 0418 0419 int k = list.indexOf(o); 0420 assert(k >= 0); 0421 0422 // Remove from hash 0423 ImagePreviewHash.remove(o.data()); 0424 0425 if (o.data() == LogObject) 0426 saveCurrentUserLog(); 0427 0428 //Remove row from the TableView model 0429 // FIXME: Is there no faster way? 0430 for (int irow = 0; irow < currentModel->rowCount(); ++irow) 0431 { 0432 QString name = currentModel->item(irow, 0)->text(); 0433 if (getObjectName(o.data()) == name) 0434 { 0435 currentModel->removeRow(irow); 0436 break; 0437 } 0438 } 0439 0440 if (!session) 0441 { 0442 obsList().removeAt(k); 0443 ui->avt->removeAllPlotObjects(); 0444 ui->WishListView->resizeColumnsToContents(); 0445 if (!update) 0446 slotSaveList(); 0447 } 0448 else 0449 { 0450 if (!update) 0451 TimeHash.remove(o->name()); 0452 sessionList().removeAt(k); //Remove from the session list 0453 isModified = true; //Removing an object should trigger the modified flag 0454 ui->avt->removeAllPlotObjects(); 0455 ui->SessionView->resizeColumnsToContents(); 0456 SkyMap::Instance()->forceUpdate(); 0457 } 0458 } 0459 0460 void ObservingList::slotRemoveSelectedObjects() 0461 { 0462 //Find each object by name in the session list, and remove it 0463 //Go backwards so item alignment doesn't get screwed up as rows are removed. 0464 for (int irow = getActiveModel()->rowCount() - 1; irow >= 0; --irow) 0465 { 0466 bool rowSelected; 0467 if (sessionView) 0468 rowSelected = ui->SessionView->selectionModel()->isRowSelected(irow, QModelIndex()); 0469 else 0470 rowSelected = ui->WishListView->selectionModel()->isRowSelected(irow, QModelIndex()); 0471 0472 if (rowSelected) 0473 { 0474 QModelIndex sortIndex, index; 0475 sortIndex = getActiveSortModel()->index(irow, 0); 0476 index = getActiveSortModel()->mapToSource(sortIndex); 0477 SkyObject *o = static_cast<SkyObject *>(index.data(Qt::UserRole + 1).value<void *>()); 0478 Q_ASSERT(o); 0479 slotRemoveObject(o, sessionView); 0480 } 0481 } 0482 0483 if (sessionView) 0484 { 0485 //we've removed all selected objects, so clear the selection 0486 ui->SessionView->selectionModel()->clear(); 0487 //Update the lists in the Execute window as well 0488 KStarsData::Instance()->executeSession()->init(); 0489 } 0490 0491 setSaveImagesButton(); 0492 ui->ImagePreview->setCursor(Qt::ArrowCursor); 0493 } 0494 0495 void ObservingList::slotNewSelection() 0496 { 0497 bool found = false; 0498 singleSelection = false; 0499 noSelection = false; 0500 showScope = false; 0501 //ui->ImagePreview->clearPreview(); 0502 //ui->ImagePreview->setPixmap(QPixmap()); 0503 ui->ImagePreview->setCursor(Qt::ArrowCursor); 0504 QModelIndexList selectedItems; 0505 QString newName; 0506 QSharedPointer<SkyObject> o; 0507 QString labelText; 0508 ui->DeleteImage->setEnabled(false); 0509 0510 selectedItems = 0511 getActiveSortModel()->mapSelectionToSource(getActiveView()->selectionModel()->selection()).indexes(); 0512 0513 if (selectedItems.size() == getActiveModel()->columnCount()) 0514 { 0515 newName = selectedItems[0].data().toString(); 0516 singleSelection = true; 0517 //Find the selected object in the SessionList, 0518 //then break the loop. Now SessionList.current() 0519 //points to the new selected object (until now it was the previous object) 0520 for (auto &o_temp : getActiveList()) 0521 { 0522 if (getObjectName(o_temp.data()) == newName) 0523 { 0524 o = o_temp; 0525 found = true; 0526 break; 0527 } 0528 } 0529 } 0530 0531 if (singleSelection) 0532 { 0533 //Enable buttons 0534 ui->ImagePreview->setCursor(Qt::PointingHandCursor); 0535 #ifdef HAVE_INDI 0536 showScope = true; 0537 #endif 0538 if (found) 0539 { 0540 m_CurrentObject = o.data(); 0541 //QPoint pos(0,0); 0542 plot(o.data()); 0543 //Change the m_currentImageFileName, DSS/SDSS Url to correspond to the new object 0544 setCurrentImage(o.data()); 0545 ui->SearchImage->setEnabled(true); 0546 if (currentObject()->hasName()) 0547 { 0548 //Display the current object's user notes in the NotesEdit 0549 //First, save the last object's user log to disk, if necessary 0550 saveCurrentUserLog(); //uses LogObject, which is still the previous obj. 0551 //set LogObject to the new selected object 0552 LogObject = currentObject(); 0553 ui->NotesEdit->setEnabled(true); 0554 0555 const auto &userLog = 0556 KStarsData::Instance()->getUserData(LogObject->name()).userLog; 0557 0558 if (userLog.isEmpty()) 0559 { 0560 ui->NotesEdit->setPlainText( 0561 i18n("Record here observation logs and/or data on %1.", getObjectName(LogObject))); 0562 } 0563 else 0564 { 0565 ui->NotesEdit->setPlainText(userLog); 0566 } 0567 if (sessionView) 0568 { 0569 ui->TimeEdit->setEnabled(true); 0570 ui->SetTime->setEnabled(true); 0571 ui->TimeEdit->setTime(TimeHash.value(o->name(), o->transitTime(dt, geo))); 0572 } 0573 } 0574 else //selected object is named "star" 0575 { 0576 //clear the log text box 0577 saveCurrentUserLog(); 0578 ui->NotesEdit->clear(); 0579 ui->NotesEdit->setEnabled(false); 0580 ui->SearchImage->setEnabled(false); 0581 } 0582 QString ImagePath = KSPaths::locate(QStandardPaths::AppLocalDataLocation, m_currentImageFileName); 0583 if (!ImagePath.isEmpty()) 0584 { 0585 //If the image is present, show it! 0586 KSDssImage ksdi(ImagePath); 0587 KSDssImage::Metadata md = ksdi.getMetadata(); 0588 //ui->ImagePreview->showPreview( QUrl::fromLocalFile( ksdi.getFileName() ) ); 0589 if (ImagePreviewHash.contains(o.data()) == false) 0590 ImagePreviewHash[o.data()] = QPixmap(ksdi.getFileName()).scaledToHeight(ui->ImagePreview->width()); 0591 0592 //ui->ImagePreview->setPixmap(QPixmap(ksdi.getFileName()).scaledToHeight(ui->ImagePreview->width())); 0593 ui->ImagePreview->setPixmap(ImagePreviewHash[o.data()]); 0594 if (md.isValid()) 0595 { 0596 ui->dssMetadataLabel->setText( 0597 i18n("DSS Image metadata: \n Size: %1\' x %2\' \n Photometric band: %3 \n Version: %4", 0598 QString::number(md.width), QString::number(md.height), QString() + md.band, md.version)); 0599 } 0600 else 0601 ui->dssMetadataLabel->setText(i18n("No image info available.")); 0602 ui->ImagePreview->show(); 0603 ui->DeleteImage->setEnabled(true); 0604 } 0605 else 0606 { 0607 setDefaultImage(); 0608 ui->dssMetadataLabel->setText( 0609 i18n("No image available. Click on the placeholder image to download one.")); 0610 } 0611 QString cname = 0612 KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(o.data()); 0613 if (o->type() != SkyObject::CONSTELLATION) 0614 { 0615 labelText = "<b>"; 0616 if (o->type() == SkyObject::PLANET) 0617 labelText += o->translatedName(); 0618 else 0619 labelText += o->name(); 0620 if (std::isfinite(o->mag()) && o->mag() <= 30.) 0621 labelText += ":</b> " + i18nc("%1 magnitude of object, %2 type of sky object (planet, asteroid " 0622 "etc), %3 name of a constellation", 0623 "%1 mag %2 in %3", o->mag(), o->typeName().toLower(), cname); 0624 else 0625 labelText += 0626 ":</b> " + i18nc("%1 type of sky object (planet, asteroid etc), %2 name of a constellation", 0627 "%1 in %2", o->typeName(), cname); 0628 } 0629 } 0630 else 0631 { 0632 setDefaultImage(); 0633 qCWarning(KSTARS) << "Object " << newName << " not found in list."; 0634 } 0635 ui->quickInfoLabel->setText(labelText); 0636 } 0637 else 0638 { 0639 if (selectedItems.isEmpty()) //Nothing selected 0640 { 0641 //Disable buttons 0642 noSelection = true; 0643 ui->NotesEdit->setEnabled(false); 0644 m_CurrentObject = nullptr; 0645 ui->TimeEdit->setEnabled(false); 0646 ui->SetTime->setEnabled(false); 0647 ui->SearchImage->setEnabled(false); 0648 //Clear the user log text box. 0649 saveCurrentUserLog(); 0650 ui->NotesEdit->setPlainText(""); 0651 //Clear the plot in the AVTPlotwidget 0652 ui->avt->removeAllPlotObjects(); 0653 } 0654 else //more than one object selected. 0655 { 0656 ui->NotesEdit->setEnabled(false); 0657 ui->TimeEdit->setEnabled(false); 0658 ui->SetTime->setEnabled(false); 0659 ui->SearchImage->setEnabled(false); 0660 m_CurrentObject = nullptr; 0661 //Clear the plot in the AVTPlotwidget 0662 ui->avt->removeAllPlotObjects(); 0663 //Clear the user log text box. 0664 saveCurrentUserLog(); 0665 ui->NotesEdit->setPlainText(""); 0666 ui->quickInfoLabel->setText(QString()); 0667 } 0668 } 0669 } 0670 0671 void ObservingList::slotCenterObject() 0672 { 0673 if (getSelectedItems().size() == 1) 0674 { 0675 SkyMap::Instance()->setClickedObject(currentObject()); 0676 SkyMap::Instance()->setClickedPoint(currentObject()); 0677 SkyMap::Instance()->slotCenter(); 0678 } 0679 } 0680 0681 void ObservingList::slotSlewToObject() 0682 { 0683 #ifdef HAVE_INDI 0684 0685 if (INDIListener::Instance()->size() == 0) 0686 { 0687 KSNotification::sorry(i18n("No connected mounts found.")); 0688 return; 0689 } 0690 0691 for (auto &oneDevice : INDIListener::devices()) 0692 { 0693 if (!(oneDevice->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE)) 0694 continue; 0695 0696 if (oneDevice->isConnected() == false) 0697 { 0698 KSNotification::error(i18n("Mount %1 is offline. Please connect and retry again.", oneDevice->getDeviceName())); 0699 return; 0700 } 0701 0702 auto mount = oneDevice->getMount(); 0703 if (!mount) 0704 continue; 0705 mount->Slew(currentObject()); 0706 return; 0707 } 0708 0709 KSNotification::sorry(i18n("No connected mounts found.")); 0710 0711 #endif 0712 } 0713 0714 void ObservingList::slotAddToEkosScheduler() 0715 { 0716 #ifdef HAVE_INDI 0717 Ekos::Manager::Instance()->addObjectToScheduler(currentObject()); 0718 #endif 0719 } 0720 0721 //FIXME: This will open multiple Detail windows for each object; 0722 //Should have one window whose target object changes with selection 0723 void ObservingList::slotDetails() 0724 { 0725 if (currentObject()) 0726 { 0727 QPointer<DetailDialog> dd = 0728 new DetailDialog(currentObject(), KStarsData::Instance()->ut(), geo, KStars::Instance()); 0729 dd->exec(); 0730 delete dd; 0731 } 0732 } 0733 0734 void ObservingList::slotWUT() 0735 { 0736 KStarsDateTime lt = dt; 0737 lt.setTime(QTime(8, 0, 0)); 0738 QPointer<WUTDialog> w = new WUTDialog(KStars::Instance(), sessionView, geo, lt); 0739 w->exec(); 0740 delete w; 0741 } 0742 0743 void ObservingList::slotAddToSession() 0744 { 0745 Q_ASSERT(!sessionView); 0746 if (getSelectedItems().size()) 0747 { 0748 foreach (const QModelIndex &i, getSelectedItems()) 0749 { 0750 foreach (QSharedPointer<SkyObject> o, obsList()) 0751 if (getObjectName(o.data()) == i.data().toString()) 0752 slotAddObject( 0753 o.data(), 0754 true); // FIXME: Would be good to have a wrapper that accepts QSharedPointer<SkyObject> 0755 } 0756 } 0757 } 0758 0759 void ObservingList::slotFind() 0760 { 0761 if (FindDialog::Instance()->exec() == QDialog::Accepted) 0762 { 0763 SkyObject *o = FindDialog::Instance()->targetObject(); 0764 if (o != nullptr) 0765 { 0766 slotAddObject(o, sessionView); 0767 } 0768 } 0769 } 0770 0771 void ObservingList::slotBatchAdd() 0772 { 0773 bool accepted = false; 0774 QString items = QInputDialog::getMultiLineText(this, 0775 sessionView ? i18n("Batch add to observing session") : i18n("Batch add to observing wishlist"), 0776 i18n("Specify a list of objects with one object on each line to add. The names must be understood to KStars, or if the internet resolver is enabled in settings, to the CDS Sesame resolver. Objects that are internet resolved will be added to the database."), 0777 QString(), 0778 &accepted); 0779 bool resolve = Options::resolveNamesOnline(); 0780 0781 if (accepted && !items.isEmpty()) 0782 { 0783 QStringList failedObjects; 0784 QStringList objectNames = items.split("\n"); 0785 for (QString objectName : objectNames) 0786 { 0787 objectName = FindDialog::processSearchText(objectName); 0788 SkyObject *object = KStarsData::Instance()->objectNamed(objectName); 0789 if (!object && resolve) 0790 { 0791 object = FindDialog::resolveAndAdd(m_manager, objectName); 0792 } 0793 if (!object) 0794 { 0795 failedObjects.append(objectName); 0796 } 0797 else 0798 { 0799 slotAddObject(object, sessionView); 0800 } 0801 } 0802 0803 if (!failedObjects.isEmpty()) 0804 { 0805 QMessageBox msgBox = 0806 { 0807 QMessageBox::Icon::Warning, 0808 i18np("Batch add: %1 object not found", "Batch add: %1 objects not found", failedObjects.size()), 0809 i18np("%1 object could not be found in the database or resolved, and hence could not be added. See the details for more.", 0810 "%1 objects could not be found in the database or resolved, and hence could not be added. See the details for more.", 0811 failedObjects.size()), 0812 QMessageBox::Ok, 0813 this 0814 }; 0815 msgBox.setDetailedText(failedObjects.join("\n")); 0816 msgBox.exec(); 0817 } 0818 } 0819 Q_ASSERT(false); // Not implemented 0820 } 0821 0822 void ObservingList::slotEyepieceView() 0823 { 0824 KStars::Instance()->slotEyepieceView(currentObject(), getCurrentImagePath()); 0825 } 0826 0827 void ObservingList::slotAVT() 0828 { 0829 QModelIndexList selectedItems; 0830 // TODO: Think and see if there's a more efficient way to do this. I can't seem to think of any, but this code looks like it could be improved. - Akarsh 0831 selectedItems = 0832 (sessionView ? 0833 m_SessionSortModel->mapSelectionToSource(ui->SessionView->selectionModel()->selection()).indexes() : 0834 m_WishListSortModel->mapSelectionToSource(ui->WishListView->selectionModel()->selection()).indexes()); 0835 0836 if (selectedItems.size()) 0837 { 0838 QPointer<AltVsTime> avt = new AltVsTime(KStars::Instance()); 0839 foreach (const QModelIndex &i, selectedItems) 0840 { 0841 if (i.column() == 0) 0842 { 0843 SkyObject *o = static_cast<SkyObject *>(i.data(Qt::UserRole + 1).value<void *>()); 0844 Q_ASSERT(o); 0845 avt->processObject(o); 0846 } 0847 } 0848 avt->exec(); 0849 delete avt; 0850 } 0851 } 0852 0853 //FIXME: On close, we will need to close any open Details/AVT windows 0854 void ObservingList::slotClose() 0855 { 0856 //Save the current User log text 0857 saveCurrentUserLog(); 0858 ui->avt->removeAllPlotObjects(); 0859 slotNewSelection(); 0860 saveCurrentList(); 0861 hide(); 0862 } 0863 0864 void ObservingList::saveCurrentUserLog() 0865 { 0866 if (LogObject && !ui->NotesEdit->toPlainText().isEmpty() && 0867 ui->NotesEdit->toPlainText() != 0868 i18n("Record here observation logs and/or data on %1.", getObjectName(LogObject))) 0869 { 0870 const auto &success = KStarsData::Instance()->updateUserLog( 0871 LogObject->name(), ui->NotesEdit->toPlainText()); 0872 0873 if (!success.first) 0874 KSNotification::sorry(success.second, i18n("Could not update the user log.")); 0875 0876 ui->NotesEdit->clear(); 0877 LogObject = nullptr; 0878 } 0879 } 0880 0881 void ObservingList::slotOpenList() 0882 { 0883 QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18nc("@title:window", "Open Observing List"), QUrl(), 0884 "KStars Observing List (*.obslist)"); 0885 QFile f; 0886 0887 if (fileURL.isValid()) 0888 { 0889 f.setFileName(fileURL.toLocalFile()); 0890 //FIXME do we still need to do this? 0891 /* 0892 if ( ! fileURL.isLocalFile() ) { 0893 //Save remote list to a temporary local file 0894 QTemporaryFile tmpfile; 0895 tmpfile.setAutoRemove(false); 0896 tmpfile.open(); 0897 m_listFileName = tmpfile.fileName(); 0898 if( KIO::NetAccess::download( fileURL, m_listFileName, this ) ) 0899 f.setFileName( m_listFileName ); 0900 0901 } else { 0902 m_listFileName = fileURL.toLocalFile(); 0903 f.setFileName( m_listFileName ); 0904 } 0905 */ 0906 0907 if (!f.open(QIODevice::ReadOnly)) 0908 { 0909 QString message = i18n("Could not open file %1", f.fileName()); 0910 KSNotification::sorry(message, i18n("Could Not Open File")); 0911 return; 0912 } 0913 saveCurrentList(); //See if the current list needs to be saved before opening the new one 0914 ui->tabWidget->setCurrentIndex(1); // FIXME: This is not robust -- asimha 0915 slotChangeTab(1); 0916 0917 sessionList().clear(); 0918 TimeHash.clear(); 0919 m_CurrentObject = nullptr; 0920 m_SessionModel->removeRows(0, m_SessionModel->rowCount()); 0921 SkyMap::Instance()->forceUpdate(); 0922 //First line is the name of the list. The rest of the file is 0923 //object names, one per line. With the TimeHash value if present 0924 QTextStream istream(&f); 0925 QString input; 0926 input = istream.readAll(); 0927 OAL::Log logObject; 0928 logObject.readBegin(input); 0929 //Set the New TimeHash 0930 TimeHash = logObject.timeHash(); 0931 GeoLocation *geo_new = logObject.geoLocation(); 0932 if (!geo_new) 0933 { 0934 // FIXME: This is a very hackish solution -- if we 0935 // encounter an invalid XML file, we know we won't read a 0936 // GeoLocation successfully. It does not detect partially 0937 // corrupt files. -- asimha 0938 KSNotification::sorry(i18n("The specified file is invalid. We expect an XML file based on the OpenAstronomyLog schema.")); 0939 f.close(); 0940 return; 0941 } 0942 dt = logObject.dateTime(); 0943 //foreach (SkyObject *o, *(logObject.targetList())) 0944 for (auto &o : logObject.targetList()) 0945 slotAddObject(o.data(), true); 0946 //Update the location and user set times from file 0947 slotUpdate(); 0948 //Newly-opened list should not trigger isModified flag 0949 isModified = false; 0950 f.close(); 0951 } 0952 else if (!fileURL.toLocalFile().isEmpty()) 0953 { 0954 KSNotification::sorry(i18n("The specified file is invalid")); 0955 } 0956 } 0957 0958 void ObservingList::slotClearList() 0959 { 0960 if ((ui->tabWidget->currentIndex() == 0 && obsList().isEmpty()) || 0961 (ui->tabWidget->currentIndex() == 1 && sessionList().isEmpty())) 0962 return; 0963 0964 QString message = i18n("Are you sure you want to clear all objects?"); 0965 if (KMessageBox::questionYesNo(this, message, i18n("Clear all?")) == KMessageBox::Yes) 0966 { 0967 // Did I forget anything else to remove? 0968 ui->avt->removeAllPlotObjects(); 0969 m_CurrentObject = LogObject = nullptr; 0970 0971 if (ui->tabWidget->currentIndex() == 0) 0972 { 0973 // IMPORTANT: Is this enough or we will have dangling pointers in memory? 0974 ImagePreviewHash.clear(); 0975 obsList().clear(); 0976 m_WishListModel->setRowCount(0); 0977 } 0978 else 0979 { 0980 // IMPORTANT: Is this enough or we will have dangling pointers in memory? 0981 sessionList().clear(); 0982 TimeHash.clear(); 0983 isModified = true; //Removing an object should trigger the modified flag 0984 m_SessionModel->setRowCount(0); 0985 SkyMap::Instance()->forceUpdate(); 0986 } 0987 } 0988 } 0989 0990 void ObservingList::saveCurrentList() 0991 { 0992 //Before loading a new list, do we need to save the current one? 0993 //Assume that if the list is empty, then there's no need to save 0994 if (sessionList().size()) 0995 { 0996 if (isModified) 0997 { 0998 QString message = i18n("Do you want to save the current session?"); 0999 if (KMessageBox::questionYesNo(this, message, i18n("Save Current session?"), KStandardGuiItem::save(), 1000 KStandardGuiItem::discard()) == KMessageBox::Yes) 1001 slotSaveSession(); 1002 } 1003 } 1004 } 1005 1006 void ObservingList::slotSaveSessionAs(bool nativeSave) 1007 { 1008 if (sessionList().isEmpty()) 1009 return; 1010 1011 QUrl fileURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save Observing List"), QUrl(), 1012 "KStars Observing List (*.obslist)"); 1013 if (fileURL.isValid()) 1014 { 1015 m_listFileName = fileURL.toLocalFile(); 1016 slotSaveSession(nativeSave); 1017 } 1018 } 1019 1020 void ObservingList::slotSaveList() 1021 { 1022 QFile f; 1023 // FIXME: Move wishlist into a database. 1024 // TODO: Support multiple wishlists. 1025 1026 QString fileContents; 1027 QTextStream ostream( 1028 &fileContents); // We first write to a QString to prevent truncating the file in case there is a crash. 1029 foreach (const QSharedPointer<SkyObject> o, obsList()) 1030 { 1031 if (!o) 1032 { 1033 qWarning() << "Null entry in observing wishlist! Skipping!"; 1034 continue; 1035 } 1036 if (o->name() == "star") 1037 { 1038 //ostream << o->name() << " " << o->ra0().Hours() << " " << o->dec0().Degrees() << Qt::endl; 1039 ostream << getObjectName(o.data(), false) << '\n'; 1040 } 1041 else if (o->type() == SkyObject::STAR) 1042 { 1043 Q_ASSERT(dynamic_cast<const StarObject *>(o.data())); 1044 const QSharedPointer<StarObject> s = qSharedPointerCast<StarObject>(o); 1045 if (s->name() == s->gname()) 1046 ostream << s->name2() << '\n'; 1047 else 1048 ostream << s->name() << '\n'; 1049 } 1050 else 1051 { 1052 ostream << o->name() << '\n'; 1053 } 1054 } 1055 f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("wishlist.obslist")); 1056 if (!f.open(QIODevice::WriteOnly)) 1057 { 1058 qWarning() << "Cannot save wish list to file!"; // TODO: This should be presented as a message box to the user 1059 KMessageBox::error(this, 1060 i18n("Could not open the observing wishlist file %1 for writing. Your wishlist changes will not be saved. Check if the location is writable and not full.", 1061 f.fileName()), i18n("Could not save observing wishlist")); 1062 return; 1063 } 1064 QTextStream writeemall(&f); 1065 writeemall << fileContents; 1066 f.close(); 1067 } 1068 1069 void ObservingList::slotLoadWishList() 1070 { 1071 QFile f; 1072 f.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("wishlist.obslist")); 1073 if (!f.open(QIODevice::ReadOnly)) 1074 { 1075 qWarning(KSTARS) << "No WishList Saved yet"; 1076 return; 1077 } 1078 QTextStream istream(&f); 1079 QString line; 1080 1081 QPointer<QProgressDialog> addingObjectsProgress = new QProgressDialog(); 1082 addingObjectsProgress->setWindowTitle(i18nc("@title:window", "Observing List Wizard")); 1083 addingObjectsProgress->setLabelText(i18n("Please wait while loading observing wishlist...")); 1084 1085 1086 // Read the entire file in one pass so we can show better progress indication 1087 QStringList objects; 1088 while (!istream.atEnd()) 1089 { 1090 objects.append(istream.readLine()); 1091 } 1092 addingObjectsProgress->setMaximum(objects.size()); 1093 addingObjectsProgress->setMinimum(0); 1094 addingObjectsProgress->show(); 1095 1096 QStringList failedObjects; 1097 for (std::size_t idx = 0; idx < objects.size(); ++idx) 1098 { 1099 const auto &objectName = objects[idx]; 1100 1101 if (addingObjectsProgress->wasCanceled()) 1102 { 1103 QMessageBox msgBox = 1104 { 1105 QMessageBox::Icon::Warning, 1106 i18n("Canceling this will truncate your wishlist"), 1107 i18n("If you cancel this operation, your wishlist will be truncated and the following objects will be removed from the wishlist when you exit KStars. Are you sure this is okay?"), 1108 QMessageBox::Yes | QMessageBox::No, 1109 this 1110 }; 1111 msgBox.setDefaultButton(QMessageBox::No); 1112 msgBox.setDetailedText(objects.mid(idx).join("\n") + "\n"); 1113 if (msgBox.exec() == QMessageBox::Yes) 1114 break; 1115 else 1116 { 1117 addingObjectsProgress->reset(); 1118 addingObjectsProgress->setValue(idx); 1119 addingObjectsProgress->show(); 1120 } 1121 1122 } 1123 1124 SkyObject *o = KStarsData::Instance()->objectNamed(objectName); 1125 1126 //If we haven't identified the object, try interpreting the 1127 //name as a star's genetive name (with ascii letters) 1128 if (!o) 1129 o = KStarsData::Instance()->skyComposite()->findStarByGenetiveName(line); 1130 1131 if (o) 1132 { 1133 slotAddObject(o, false, true); 1134 } 1135 else 1136 { 1137 failedObjects.append(line); 1138 } 1139 1140 addingObjectsProgress->setValue(idx + 1); 1141 qApp->processEvents(); 1142 } 1143 delete (addingObjectsProgress); 1144 f.close(); 1145 1146 if (!failedObjects.isEmpty()) 1147 { 1148 QMessageBox msgBox = {QMessageBox::Icon::Warning, 1149 i18np("Observing wishlist truncated: %1 object not found", "Observing wishlist truncated: %1 objects not found", failedObjects.size()), 1150 i18np("%1 object could not be found in the database, and will be removed from the observing wish list. We recommend that you copy its name as a backup so you can add it later.", "%1 objects could not be found in the database, and will be removed from the observing wish list. We recommend that you copy the detailed list as a backup, whereby you can later use the Batch Add feature in the Observation Planner to add them back using internet search.", failedObjects.size()), 1151 QMessageBox::Ok, 1152 this 1153 }; 1154 msgBox.setDetailedText(failedObjects.join("\n") + "\n"); 1155 msgBox.exec(); 1156 } 1157 } 1158 1159 void ObservingList::slotSaveSession(bool nativeSave) 1160 { 1161 if (sessionList().isEmpty()) 1162 { 1163 KSNotification::error(i18n("Cannot save an empty session list.")); 1164 return; 1165 } 1166 1167 if (m_listFileName.isEmpty()) 1168 { 1169 slotSaveSessionAs(nativeSave); 1170 return; 1171 } 1172 QFile f(m_listFileName); 1173 if (!f.open(QIODevice::WriteOnly)) 1174 { 1175 QString message = i18n("Could not open file %1. Try a different filename?", f.fileName()); 1176 if (KMessageBox::warningYesNo(nullptr, message, i18n("Could Not Open File"), KGuiItem(i18n("Try Different")), 1177 KGuiItem(i18n("Do Not Try"))) == KMessageBox::Yes) 1178 { 1179 m_listFileName.clear(); 1180 slotSaveSessionAs(nativeSave); 1181 } 1182 return; 1183 } 1184 QTextStream ostream(&f); 1185 OAL::Log log; 1186 ostream << log.writeLog(nativeSave); 1187 f.close(); 1188 isModified = false; //We've saved the session, so reset the modified flag. 1189 } 1190 1191 void ObservingList::slotWizard() 1192 { 1193 QPointer<ObsListWizard> wizard = new ObsListWizard(KStars::Instance()); 1194 if (wizard->exec() == QDialog::Accepted) 1195 { 1196 QPointer<QProgressDialog> addingObjectsProgress = new QProgressDialog(); 1197 addingObjectsProgress->setWindowTitle(i18nc("@title:window", "Observing List Wizard")); 1198 addingObjectsProgress->setLabelText(i18n("Please wait while adding objects...")); 1199 addingObjectsProgress->setMaximum(wizard->obsList().size()); 1200 addingObjectsProgress->setMinimum(0); 1201 addingObjectsProgress->setValue(0); 1202 addingObjectsProgress->show(); 1203 int counter = 1; 1204 foreach (SkyObject *o, wizard->obsList()) 1205 { 1206 slotAddObject(o); 1207 addingObjectsProgress->setValue(counter++); 1208 if (addingObjectsProgress->wasCanceled()) 1209 break; 1210 qApp->processEvents(); 1211 } 1212 delete addingObjectsProgress; 1213 } 1214 1215 delete wizard; 1216 } 1217 1218 void ObservingList::plot(SkyObject *o) 1219 { 1220 if (!o) 1221 return; 1222 float DayOffset = 0; 1223 if (TimeHash.value(o->name(), o->transitTime(dt, geo)).hour() > 12) 1224 DayOffset = 1; 1225 1226 QDateTime midnight = QDateTime(dt.date(), QTime()); 1227 KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight)); 1228 double h1 = geo->GSTtoLST(ut.gst()).Hours(); 1229 if (h1 > 12.0) 1230 h1 -= 24.0; 1231 1232 ui->avt->setSecondaryLimits(h1, h1 + 24.0, -90.0, 90.0); 1233 ksal->setLocation(geo); 1234 ksal->setDate(ut); 1235 ui->avt->setGeoLocation(geo); 1236 ui->avt->setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet()); 1237 ui->avt->setDawnDuskTimes(ksal->getDawnAstronomicalTwilight(), ksal->getDuskAstronomicalTwilight()); 1238 ui->avt->setMinMaxSunAlt(ksal->getSunMinAlt(), ksal->getSunMaxAlt()); 1239 ui->avt->setMoonRiseSetTimes(ksal->getMoonRise(), ksal->getMoonSet()); 1240 ui->avt->setMoonIllum(ksal->getMoonIllum()); 1241 ui->avt->update(); 1242 KPlotObject *po = new KPlotObject(Qt::white, KPlotObject::Lines, 2.0); 1243 for (double h = -12.0; h <= 12.0; h += 0.5) 1244 { 1245 po->addPoint(h, findAltitude(o, (h + DayOffset * 24.0))); 1246 } 1247 ui->avt->removeAllPlotObjects(); 1248 ui->avt->addPlotObject(po); 1249 } 1250 1251 double ObservingList::findAltitude(SkyPoint *p, double hour) 1252 { 1253 // Jasem 2015-09-05 Using correct procedure to find altitude 1254 SkyPoint sp = *p; // make a copy 1255 QDateTime midnight = QDateTime(dt.date(), QTime()); 1256 KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight)); 1257 KStarsDateTime targetDateTime = ut.addSecs(hour * 3600.0); 1258 dms LST = geo->GSTtoLST(targetDateTime.gst()); 1259 sp.EquatorialToHorizontal(&LST, geo->lat()); 1260 return sp.alt().Degrees(); 1261 } 1262 1263 void ObservingList::slotChangeTab(int index) 1264 { 1265 noSelection = true; 1266 saveCurrentUserLog(); 1267 ui->NotesEdit->setEnabled(false); 1268 ui->TimeEdit->setEnabled(false); 1269 ui->SetTime->setEnabled(false); 1270 ui->SearchImage->setEnabled(false); 1271 ui->DeleteImage->setEnabled(false); 1272 m_CurrentObject = nullptr; 1273 sessionView = index != 0; 1274 setSaveImagesButton(); 1275 ui->WizardButton->setEnabled(!sessionView); //wizard adds only to the Wish List 1276 ui->OALExport->setEnabled(sessionView); 1277 //Clear the selection in the Tables 1278 ui->WishListView->clearSelection(); 1279 ui->SessionView->clearSelection(); 1280 //Clear the user log text box. 1281 saveCurrentUserLog(); 1282 ui->NotesEdit->setPlainText(""); 1283 ui->avt->removeAllPlotObjects(); 1284 } 1285 1286 void ObservingList::slotLocation() 1287 { 1288 QPointer<LocationDialog> ld = new LocationDialog(this); 1289 if (ld->exec() == QDialog::Accepted) 1290 { 1291 geo = ld->selectedCity(); 1292 ui->SetLocation->setText(geo->fullName()); 1293 } 1294 delete ld; 1295 } 1296 1297 void ObservingList::slotUpdate() 1298 { 1299 dt.setDate(ui->DateEdit->date()); 1300 ui->avt->removeAllPlotObjects(); 1301 //Creating a copy of the lists, we can't use the original lists as they'll keep getting modified as the loop iterates 1302 QList<QSharedPointer<SkyObject>> _obsList = m_WishList, _SessionList = m_SessionList; 1303 1304 for (QSharedPointer<SkyObject> &o : _obsList) 1305 { 1306 if (o->name() != "star") 1307 { 1308 slotRemoveObject(o.data(), false, true); 1309 slotAddObject(o.data(), false, true); 1310 } 1311 } 1312 for (QSharedPointer<SkyObject> &obj : _SessionList) 1313 { 1314 if (obj->name() != "star") 1315 { 1316 slotRemoveObject(obj.data(), true, true); 1317 slotAddObject(obj.data(), true, true); 1318 } 1319 } 1320 SkyMap::Instance()->forceUpdate(); 1321 } 1322 1323 void ObservingList::slotSetTime() 1324 { 1325 SkyObject *o = currentObject(); 1326 slotRemoveObject(o, true); 1327 TimeHash[o->name()] = ui->TimeEdit->time(); 1328 slotAddObject(o, true, true); 1329 } 1330 1331 void ObservingList::slotCustomDSS() 1332 { 1333 ui->SearchImage->setEnabled(false); 1334 //ui->ImagePreview->clearPreview(); 1335 ui->ImagePreview->setPixmap(QPixmap()); 1336 1337 KSDssImage::Metadata md; 1338 bool ok = true; 1339 1340 int width = QInputDialog::getInt(this, i18n("Customized DSS Download"), i18n("Specify image width (arcminutes): "), 1341 15, 15, 75, 1, &ok); 1342 int height = QInputDialog::getInt(this, i18n("Customized DSS Download"), 1343 i18n("Specify image height (arcminutes): "), 15, 15, 75, 1, &ok); 1344 QStringList strList = (QStringList() << "poss2ukstu_blue" 1345 << "poss2ukstu_red" 1346 << "poss2ukstu_ir" 1347 << "poss1_blue" 1348 << "poss1_red" 1349 << "quickv" 1350 << "all"); 1351 QString version = 1352 QInputDialog::getItem(this, i18n("Customized DSS Download"), i18n("Specify version: "), strList, 0, false, &ok); 1353 1354 QUrl srcUrl(KSDssDownloader::getDSSURL(currentObject()->ra0(), currentObject()->dec0(), width, height, "gif", 1355 version, &md)); 1356 1357 delete m_dl; 1358 m_dl = new KSDssDownloader(); 1359 connect(m_dl, SIGNAL(downloadComplete(bool)), SLOT(downloadReady(bool))); 1360 m_dl->startSingleDownload(srcUrl, getCurrentImagePath(), md); 1361 } 1362 1363 void ObservingList::slotGetImage(bool _dss, const SkyObject *o) 1364 { 1365 dss = _dss; 1366 if (!o) 1367 o = currentObject(); 1368 ui->SearchImage->setEnabled(false); 1369 setCurrentImage(o); 1370 QString currentImagePath = getCurrentImagePath(); 1371 if (QFile::exists(currentImagePath)) 1372 QFile::remove(currentImagePath); 1373 //QUrl url; 1374 dss = true; 1375 std::function<void(bool)> slot = std::bind(&ObservingList::downloadReady, this, std::placeholders::_1); 1376 new KSDssDownloader(o, currentImagePath, slot, this); 1377 } 1378 1379 void ObservingList::downloadReady(bool success) 1380 { 1381 // set downloadJob to 0, but don't delete it - the job will be deleted automatically 1382 // downloadJob = 0; 1383 1384 delete m_dl; 1385 m_dl = nullptr; // required if we came from slotCustomDSS; does nothing otherwise 1386 1387 if (!success) 1388 { 1389 KSNotification::sorry(i18n("Failed to download DSS/SDSS image.")); 1390 } 1391 else 1392 { 1393 /* 1394 if( QFile( QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(m_currentImageFileName) ).size() > 13000) 1395 //The default image is around 8689 bytes 1396 */ 1397 //ui->ImagePreview->showPreview( QUrl::fromLocalFile( getCurrentImagePath() ) ); 1398 ui->ImagePreview->setPixmap(QPixmap(getCurrentImagePath()).scaledToHeight(ui->ImagePreview->width())); 1399 saveThumbImage(); 1400 ui->ImagePreview->show(); 1401 ui->ImagePreview->setCursor(Qt::PointingHandCursor); 1402 ui->DeleteImage->setEnabled(true); 1403 } 1404 /* 1405 // FIXME: Implement a priority order SDSS > DSS in the DSS downloader 1406 else if( ! dss ) 1407 slotGetImage( true ); 1408 */ 1409 } 1410 1411 void ObservingList::setCurrentImage(const SkyObject *o) 1412 { 1413 QString sanitizedName = o->name().remove(' ').remove('\'').remove('\"').toLower(); 1414 1415 // JM: Always use .png across all platforms. No JPGs at all? 1416 m_currentImageFileName = "image-" + sanitizedName + ".png"; 1417 1418 m_currentThumbImageFileName = "thumb-" + sanitizedName + ".png"; 1419 1420 // Does full image exists in the path? 1421 QString currentImagePath = KSPaths::locate(QStandardPaths::AppLocalDataLocation, m_currentImageFileName); 1422 1423 // Let's try to fallback to thumb-* images if they exist 1424 if (currentImagePath.isEmpty()) 1425 { 1426 currentImagePath = KSPaths::locate(QStandardPaths::AppLocalDataLocation, m_currentThumbImageFileName); 1427 1428 // If thumb image exists, let's use it 1429 if (currentImagePath.isEmpty() == false) 1430 m_currentImageFileName = m_currentThumbImageFileName; 1431 } 1432 1433 // 2017-04-14: Unnamed stars already unsupported in observing list 1434 /* 1435 if( o->name() == "star" ) 1436 { 1437 QString RAString( o->ra0().toHMSString() ); 1438 QString DecString( o->dec0().toDMSString() ); 1439 m_currentImageFileName = "Image_J" + RAString.remove(' ').remove( ':' ) + DecString.remove(' ').remove( ':' ); // Note: Changed naming convention to standard 2016-08-25 asimha; old images shall have to be re-downloaded. 1440 // Unnecessary complication below: 1441 // QChar decsgn = ( (o->dec0().Degrees() < 0.0 ) ? '-' : '+' ); 1442 // m_currentImageFileName = m_currentImageFileName.remove('+').remove('-') + decsgn; 1443 } 1444 */ 1445 1446 // 2017-04-14 JM: If we use .png always, let us use it across all platforms. 1447 /* 1448 QString imagePath = getCurrentImagePath(); 1449 if ( QFile::exists( imagePath)) // New convention -- append filename extension so file is usable on Windows etc. 1450 { 1451 QFile::rename( imagePath, imagePath + ".png" ); 1452 } 1453 m_currentImageFileName += ".png"; 1454 */ 1455 } 1456 1457 QString ObservingList::getCurrentImagePath() 1458 { 1459 QString currentImagePath = KSPaths::locate(QStandardPaths::AppLocalDataLocation, m_currentImageFileName); 1460 if (QFile::exists(currentImagePath)) 1461 { 1462 return currentImagePath; 1463 } 1464 else 1465 return QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(m_currentImageFileName); 1466 } 1467 1468 void ObservingList::slotSaveAllImages() 1469 { 1470 ui->SearchImage->setEnabled(false); 1471 ui->DeleteImage->setEnabled(false); 1472 m_CurrentObject = nullptr; 1473 //Clear the selection in the Tables 1474 ui->WishListView->clearSelection(); 1475 ui->SessionView->clearSelection(); 1476 1477 foreach (QSharedPointer<SkyObject> o, getActiveList()) 1478 { 1479 if (!o) 1480 continue; // FIXME: Why would we have null objects? But appears that we do. 1481 setCurrentImage(o.data()); 1482 QString img(getCurrentImagePath()); 1483 // QUrl url( ( Options::obsListPreferDSS() ) ? DSSUrl : SDSSUrl ); // FIXME: We have removed SDSS support! 1484 QUrl url(KSDssDownloader::getDSSURL(o.data())); 1485 if (!o->isSolarSystem()) //TODO find a way for adding support for solar system images 1486 saveImage(url, img, o.data()); 1487 } 1488 } 1489 1490 void ObservingList::saveImage(QUrl /*url*/, QString /*filename*/, const SkyObject *o) 1491 { 1492 if (!o) 1493 o = currentObject(); 1494 Q_ASSERT(o); 1495 if (!QFile::exists(getCurrentImagePath())) 1496 { 1497 // Call the DSS downloader 1498 slotGetImage(true, o); 1499 } 1500 } 1501 1502 void ObservingList::slotImageViewer() 1503 { 1504 QPointer<ImageViewer> iv; 1505 QString currentImagePath = getCurrentImagePath(); 1506 if (QFile::exists(currentImagePath)) 1507 { 1508 QUrl url = QUrl::fromLocalFile(currentImagePath); 1509 iv = new ImageViewer(url); 1510 } 1511 1512 if (iv) 1513 iv->show(); 1514 } 1515 1516 void ObservingList::slotDeleteAllImages() 1517 { 1518 if (KMessageBox::warningYesNo(nullptr, i18n("This will delete all saved images. Are you sure you want to do this?"), 1519 i18n("Delete All Images")) == KMessageBox::No) 1520 return; 1521 ui->ImagePreview->setCursor(Qt::ArrowCursor); 1522 ui->SearchImage->setEnabled(false); 1523 ui->DeleteImage->setEnabled(false); 1524 m_CurrentObject = nullptr; 1525 //Clear the selection in the Tables 1526 ui->WishListView->clearSelection(); 1527 ui->SessionView->clearSelection(); 1528 //ui->ImagePreview->clearPreview(); 1529 ui->ImagePreview->setPixmap(QPixmap()); 1530 QDirIterator iterator(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); 1531 while (iterator.hasNext()) 1532 { 1533 // TODO: Probably, there should be a different directory for cached images in the observing list. 1534 if (iterator.fileName().contains("Image") && (!iterator.fileName().contains("dat")) && 1535 (!iterator.fileName().contains("obslist"))) 1536 { 1537 QFile file(iterator.filePath()); 1538 file.remove(); 1539 } 1540 iterator.next(); 1541 } 1542 } 1543 1544 void ObservingList::setSaveImagesButton() 1545 { 1546 ui->saveImages->setEnabled(!getActiveList().isEmpty()); 1547 } 1548 1549 // FIXME: Is there a reason to implement these as an event filter, 1550 // instead of as a signal-slot connection? Shouldn't we just use slots 1551 // to subscribe to various events from the Table / Session view? 1552 // 1553 // NOTE: ui->ImagePreview is a QLabel, which has no clicked() event or 1554 // public mouseReleaseEvent(), so eventFilter makes sense. 1555 bool ObservingList::eventFilter(QObject *obj, QEvent *event) 1556 { 1557 if (obj == ui->ImagePreview) 1558 { 1559 if (event->type() == QEvent::MouseButtonRelease) 1560 { 1561 if (currentObject()) 1562 { 1563 if (!QFile::exists(getCurrentImagePath())) 1564 { 1565 if (!currentObject()->isSolarSystem()) 1566 slotGetImage(Options::obsListPreferDSS()); 1567 else 1568 slotSearchImage(); 1569 } 1570 else 1571 slotImageViewer(); 1572 } 1573 return true; 1574 } 1575 } 1576 if (obj == ui->WishListView->viewport() || obj == ui->SessionView->viewport()) 1577 { 1578 bool sessionViewEvent = (obj == ui->SessionView->viewport()); 1579 1580 if (event->type() == QEvent::MouseButtonRelease) // Mouse button release event 1581 { 1582 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); 1583 QPoint pos(mouseEvent->globalX(), mouseEvent->globalY()); 1584 1585 if (mouseEvent->button() == Qt::RightButton) 1586 { 1587 if (!noSelection) 1588 { 1589 pmenu->initPopupMenu(sessionViewEvent, !singleSelection, showScope); 1590 pmenu->popup(pos); 1591 } 1592 return true; 1593 } 1594 } 1595 } 1596 1597 if (obj == ui->WishListView || obj == ui->SessionView) 1598 { 1599 if (event->type() == QEvent::KeyPress) 1600 { 1601 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 1602 if (keyEvent->key() == Qt::Key_Delete) 1603 { 1604 slotRemoveSelectedObjects(); 1605 return true; 1606 } 1607 } 1608 } 1609 1610 return false; 1611 } 1612 1613 void ObservingList::slotSearchImage() 1614 { 1615 QPixmap *pm = new QPixmap(":/images/noimage.png"); 1616 QPointer<ThumbnailPicker> tp = new ThumbnailPicker(currentObject(), *pm, this, 200, 200, i18n("Image Chooser")); 1617 if (tp->exec() == QDialog::Accepted) 1618 { 1619 QString currentImagePath = getCurrentImagePath(); 1620 QFile f(currentImagePath); 1621 1622 //If a real image was set, save it. 1623 if (tp->imageFound()) 1624 { 1625 const auto image = *tp->image(); 1626 image.save(f.fileName(), "PNG"); 1627 //ui->ImagePreview->showPreview( QUrl::fromLocalFile( f.fileName() ) ); 1628 saveThumbImage(); 1629 slotNewSelection(); 1630 ui->ImagePreview->setPixmap(image.scaledToHeight(ui->ImagePreview->width())); 1631 ui->ImagePreview->repaint(); 1632 } 1633 } 1634 delete pm; 1635 delete tp; 1636 } 1637 1638 void ObservingList::slotDeleteCurrentImage() 1639 { 1640 QFile::remove(getCurrentImagePath()); 1641 ImagePreviewHash.remove(m_CurrentObject); 1642 slotNewSelection(); 1643 } 1644 1645 void ObservingList::saveThumbImage() 1646 { 1647 QFileInfo const f(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath( 1648 m_currentThumbImageFileName)); 1649 if (!f.exists()) 1650 { 1651 QImage img(getCurrentImagePath()); 1652 img = img.scaled(200, 200, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 1653 img.save(f.filePath()); 1654 } 1655 } 1656 1657 QString ObservingList::getTime(const SkyObject *o) const 1658 { 1659 return TimeHash.value(o->name(), QTime(30, 0, 0)).toString("h:mm:ss AP"); 1660 } 1661 1662 QTime ObservingList::scheduledTime(SkyObject *o) const 1663 { 1664 return TimeHash.value(o->name(), o->transitTime(dt, geo)); 1665 } 1666 1667 void ObservingList::setTime(const SkyObject *o, QTime t) 1668 { 1669 TimeHash.insert(o->name(), t); 1670 } 1671 1672 void ObservingList::slotOALExport() 1673 { 1674 slotSaveSessionAs(false); 1675 } 1676 1677 void ObservingList::slotAddVisibleObj() 1678 { 1679 KStarsDateTime lt = dt; 1680 lt.setTime(QTime(8, 0, 0)); 1681 QPointer<WUTDialog> w = new WUTDialog(KStars::Instance(), sessionView, geo, lt); 1682 w->init(); 1683 QModelIndexList selectedItems; 1684 selectedItems = 1685 m_WishListSortModel->mapSelectionToSource(ui->WishListView->selectionModel()->selection()).indexes(); 1686 if (selectedItems.size()) 1687 { 1688 foreach (const QModelIndex &i, selectedItems) 1689 { 1690 foreach (QSharedPointer<SkyObject> o, obsList()) 1691 if (getObjectName(o.data()) == i.data().toString() && w->checkVisibility(o.data())) 1692 slotAddObject( 1693 o.data(), 1694 true); // FIXME: Better if there is a QSharedPointer override for this, although the check will ensure that we don't duplicate. 1695 } 1696 } 1697 delete w; 1698 } 1699 1700 SkyObject *ObservingList::findObjectByName(QString name) 1701 { 1702 foreach (QSharedPointer<SkyObject> o, sessionList()) 1703 { 1704 if (getObjectName(o.data(), false) == name) 1705 return o.data(); 1706 } 1707 return nullptr; 1708 } 1709 1710 void ObservingList::selectObject(const SkyObject *o) 1711 { 1712 ui->tabWidget->setCurrentIndex(1); 1713 ui->SessionView->selectionModel()->clear(); 1714 for (int irow = m_SessionModel->rowCount() - 1; irow >= 0; --irow) 1715 { 1716 QModelIndex mSortIndex = m_SessionSortModel->index(irow, 0); 1717 QModelIndex mIndex = m_SessionSortModel->mapToSource(mSortIndex); 1718 int idxrow = mIndex.row(); 1719 if (m_SessionModel->item(idxrow, 0)->text() == getObjectName(o)) 1720 ui->SessionView->selectRow(idxrow); 1721 slotNewSelection(); 1722 } 1723 } 1724 1725 void ObservingList::setDefaultImage() 1726 { 1727 ui->ImagePreview->setPixmap(m_NoImagePixmap); 1728 ui->ImagePreview->update(); 1729 } 1730 1731 QString ObservingList::getObjectName(const SkyObject *o, bool translated) 1732 { 1733 QString finalObjectName; 1734 if (o->name() == "star") 1735 { 1736 const StarObject *s = dynamic_cast<const StarObject *>(o); 1737 1738 // JM: Enable HD Index stars to be added to the observing list. 1739 if (s != nullptr && s->getHDIndex() != 0) 1740 finalObjectName = QString("HD %1").arg(QString::number(s->getHDIndex())); 1741 } 1742 else 1743 finalObjectName = translated ? o->translatedName() : o->name(); 1744 1745 return finalObjectName; 1746 } 1747 1748 void ObservingList::slotUpdateAltitudes() 1749 { 1750 // FIXME: Update upon gaining visibility, do not update when not visible 1751 KStarsDateTime now = KStarsDateTime::currentDateTimeUtc(); 1752 // qCDebug(KSTARS) << "Updating altitudes in observation planner @ JD - J2000 = " << double( now.djd() - J2000 ); 1753 for (int irow = m_WishListModel->rowCount() - 1; irow >= 0; --irow) 1754 { 1755 QModelIndex idx = m_WishListSortModel->mapToSource(m_WishListSortModel->index(irow, 0)); 1756 SkyObject *o = static_cast<SkyObject *>(idx.data(Qt::UserRole + 1).value<void *>()); 1757 Q_ASSERT(o); 1758 SkyPoint p = o->recomputeHorizontalCoords(now, geo); 1759 idx = 1760 m_WishListSortModel->mapToSource(m_WishListSortModel->index(irow, m_WishListSortModel->columnCount() - 1)); 1761 QStandardItem *replacement = m_altCostHelper(p); 1762 m_WishListModel->setData(idx, replacement->data(Qt::DisplayRole), Qt::DisplayRole); 1763 m_WishListModel->setData(idx, replacement->data(Qt::UserRole), Qt::UserRole); 1764 delete replacement; 1765 } 1766 emit m_WishListModel->dataChanged( 1767 m_WishListModel->index(0, m_WishListModel->columnCount() - 1), 1768 m_WishListModel->index(m_WishListModel->rowCount() - 1, m_WishListModel->columnCount() - 1)); 1769 } 1770 1771 QSharedPointer<SkyObject> ObservingList::findObject(const SkyObject *o, bool session) 1772 { 1773 const QList<QSharedPointer<SkyObject>> &list = (session ? sessionList() : obsList()); 1774 const QString &target = getObjectName(o); 1775 foreach (QSharedPointer<SkyObject> obj, list) 1776 { 1777 if (getObjectName(obj.data()) == target) 1778 return obj; 1779 } 1780 return QSharedPointer<SkyObject>(); // null pointer 1781 }