Warning, file /education/kstars/kstars/skymap.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "skyobjectuserdata.h" 0008 #ifdef _WIN32 0009 #include <windows.h> 0010 #endif 0011 0012 #include "skymap.h" 0013 0014 #include "ksasteroid.h" 0015 #include "kstars_debug.h" 0016 #include "fov.h" 0017 #include "imageviewer.h" 0018 #include "xplanetimageviewer.h" 0019 #include "ksdssdownloader.h" 0020 #include "kspaths.h" 0021 #include "kspopupmenu.h" 0022 #include "kstars.h" 0023 #include "ksutils.h" 0024 #include "Options.h" 0025 #include "skymapcomposite.h" 0026 #ifdef HAVE_OPENGL 0027 #include "skymapgldraw.h" 0028 #endif 0029 #include "skymapqdraw.h" 0030 #include "starhopperdialog.h" 0031 #include "starobject.h" 0032 #include "texturemanager.h" 0033 #include "dialogs/detaildialog.h" 0034 #include "printing/printingwizard.h" 0035 #include "skycomponents/flagcomponent.h" 0036 #include "skyobjects/ksplanetbase.h" 0037 #include "skyobjects/satellite.h" 0038 #include "tools/flagmanager.h" 0039 #include "widgets/infoboxwidget.h" 0040 #include "projections/azimuthalequidistantprojector.h" 0041 #include "projections/equirectangularprojector.h" 0042 #include "projections/lambertprojector.h" 0043 #include "projections/gnomonicprojector.h" 0044 #include "projections/orthographicprojector.h" 0045 #include "projections/stereographicprojector.h" 0046 #include "catalogobject.h" 0047 #include "catalogsdb.h" 0048 #include "catalogscomponent.h" 0049 0050 #include <KActionCollection> 0051 #include <KToolBar> 0052 0053 #include <QBitmap> 0054 #include <QToolTip> 0055 #include <QClipboard> 0056 #include <QInputDialog> 0057 #include <QDesktopServices> 0058 0059 #include <QProcess> 0060 #include <QFileDialog> 0061 0062 #include <cmath> 0063 0064 namespace 0065 { 0066 // Draw bitmap for zoom cursor. Width is size of pen to draw with. 0067 QBitmap zoomCursorBitmap(int width) 0068 { 0069 QBitmap b(32, 32); 0070 b.fill(Qt::color0); 0071 int mx = 16, my = 16; 0072 // Begin drawing 0073 QPainter p; 0074 p.begin(&b); 0075 p.setPen(QPen(Qt::color1, width)); 0076 p.drawEllipse(mx - 7, my - 7, 14, 14); 0077 p.drawLine(mx + 5, my + 5, mx + 11, my + 11); 0078 p.end(); 0079 return b; 0080 } 0081 0082 // Draw bitmap for default cursor. Width is size of pen to draw with. 0083 QBitmap defaultCursorBitmap(int width) 0084 { 0085 QBitmap b(32, 32); 0086 b.fill(Qt::color0); 0087 int mx = 16, my = 16; 0088 // Begin drawing 0089 QPainter p; 0090 p.begin(&b); 0091 p.setPen(QPen(Qt::color1, width)); 0092 // 1. diagonal 0093 p.drawLine(mx - 2, my - 2, mx - 8, mx - 8); 0094 p.drawLine(mx + 2, my + 2, mx + 8, mx + 8); 0095 // 2. diagonal 0096 p.drawLine(mx - 2, my + 2, mx - 8, mx + 8); 0097 p.drawLine(mx + 2, my - 2, mx + 8, mx - 8); 0098 p.end(); 0099 return b; 0100 } 0101 0102 QBitmap circleCursorBitmap(int width) 0103 { 0104 QBitmap b(32, 32); 0105 b.fill(Qt::color0); 0106 int mx = 16, my = 16; 0107 // Begin drawing 0108 QPainter p; 0109 p.begin(&b); 0110 p.setPen(QPen(Qt::color1, width)); 0111 0112 // Circle 0113 p.drawEllipse(mx - 8, my - 8, mx, my); 0114 // 1. diagonal 0115 p.drawLine(mx - 8, my - 8, 0, 0); 0116 p.drawLine(mx + 8, my - 8, 32, 0); 0117 // 2. diagonal 0118 p.drawLine(mx - 8, my + 8, 0, 32); 0119 p.drawLine(mx + 8, my + 8, 32, 32); 0120 0121 p.end(); 0122 return b; 0123 } 0124 0125 } // namespace 0126 0127 SkyMap *SkyMap::pinstance = nullptr; 0128 0129 SkyMap *SkyMap::Create() 0130 { 0131 delete pinstance; 0132 pinstance = new SkyMap(); 0133 return pinstance; 0134 } 0135 0136 SkyMap *SkyMap::Instance() 0137 { 0138 return pinstance; 0139 } 0140 0141 SkyMap::SkyMap() 0142 : QGraphicsView(KStars::Instance()), computeSkymap(true), rulerMode(false), data(KStarsData::Instance()), pmenu(nullptr), 0143 ClickedObject(nullptr), FocusObject(nullptr), m_proj(nullptr), m_previewLegend(false), m_objPointingMode(false) 0144 { 0145 #if !defined(KSTARS_LITE) 0146 grabGesture(Qt::PinchGesture); 0147 grabGesture(Qt::TapAndHoldGesture); 0148 #endif 0149 m_Scale = 1.0; 0150 0151 ZoomRect = QRect(); 0152 0153 // set the default cursor 0154 setMouseCursorShape(static_cast<Cursor>(Options::defaultCursor())); 0155 0156 QPalette p = palette(); 0157 p.setColor(QPalette::Window, QColor(data->colorScheme()->colorNamed("SkyColor"))); 0158 setPalette(p); 0159 0160 setFocusPolicy(Qt::StrongFocus); 0161 setMinimumSize(380, 250); 0162 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); 0163 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0164 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0165 setStyleSheet("QGraphicsView { border-style: none; }"); 0166 0167 setMouseTracking(true); //Generate MouseMove events! 0168 midMouseButtonDown = false; 0169 mouseButtonDown = false; 0170 slewing = false; 0171 clockSlewing = false; 0172 0173 ClickedObject = nullptr; 0174 FocusObject = nullptr; 0175 0176 m_SkyMapDraw = nullptr; 0177 0178 pmenu = new KSPopupMenu(); 0179 0180 setupProjector(); 0181 0182 //Initialize Transient label stuff 0183 m_HoverTimer.setSingleShot(true); // using this timer as a single shot timer 0184 0185 connect(&m_HoverTimer, SIGNAL(timeout()), this, SLOT(slotTransientLabel())); 0186 connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus())); 0187 connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool))); 0188 0189 // Time infobox 0190 m_timeBox = new InfoBoxWidget(Options::shadeTimeBox(), Options::positionTimeBox(), Options::stickyTimeBox(), 0191 QStringList(), this); 0192 m_timeBox->setVisible(Options::showTimeBox()); 0193 connect(data->clock(), SIGNAL(timeChanged()), m_timeBox, SLOT(slotTimeChanged())); 0194 connect(data->clock(), SIGNAL(timeAdvanced()), m_timeBox, SLOT(slotTimeChanged())); 0195 0196 // Geo infobox 0197 m_geoBox = new InfoBoxWidget(Options::shadeGeoBox(), Options::positionGeoBox(), Options::stickyGeoBox(), 0198 QStringList(), this); 0199 m_geoBox->setVisible(Options::showGeoBox()); 0200 connect(data, SIGNAL(geoChanged()), m_geoBox, SLOT(slotGeoChanged())); 0201 0202 // Object infobox 0203 m_objBox = new InfoBoxWidget(Options::shadeFocusBox(), Options::positionFocusBox(), Options::stickyFocusBox(), 0204 QStringList(), this); 0205 m_objBox->setVisible(Options::showFocusBox()); 0206 connect(this, &SkyMap::objectChanged, m_objBox, &InfoBoxWidget::slotObjectChanged); 0207 connect(this, &SkyMap::positionChanged, m_objBox, &InfoBoxWidget::slotPointChanged); 0208 0209 m_SkyMapDraw = new SkyMapQDraw(this); 0210 m_SkyMapDraw->setMouseTracking(true); 0211 0212 m_SkyMapDraw->setParent(this->viewport()); 0213 m_SkyMapDraw->show(); 0214 0215 m_iboxes = new InfoBoxes(m_SkyMapDraw); 0216 0217 m_iboxes->setVisible(Options::showInfoBoxes()); 0218 m_iboxes->addInfoBox(m_timeBox); 0219 m_iboxes->addInfoBox(m_geoBox); 0220 m_iboxes->addInfoBox(m_objBox); 0221 } 0222 0223 void SkyMap::slotToggleGeoBox(bool flag) 0224 { 0225 m_geoBox->setVisible(flag); 0226 } 0227 0228 void SkyMap::slotToggleFocusBox(bool flag) 0229 { 0230 m_objBox->setVisible(flag); 0231 } 0232 0233 void SkyMap::slotToggleTimeBox(bool flag) 0234 { 0235 m_timeBox->setVisible(flag); 0236 } 0237 0238 void SkyMap::slotToggleInfoboxes(bool flag) 0239 { 0240 m_iboxes->setVisible(flag); 0241 Options::setShowInfoBoxes(flag); 0242 } 0243 0244 SkyMap::~SkyMap() 0245 { 0246 /* == Save infoxes status into Options == */ 0247 //Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget())); 0248 // Time box 0249 Options::setPositionTimeBox(m_timeBox->pos()); 0250 Options::setShadeTimeBox(m_timeBox->shaded()); 0251 Options::setStickyTimeBox(m_timeBox->sticky()); 0252 Options::setShowTimeBox(m_timeBox->isVisibleTo(m_iboxes)); 0253 // Geo box 0254 Options::setPositionGeoBox(m_geoBox->pos()); 0255 Options::setShadeGeoBox(m_geoBox->shaded()); 0256 Options::setStickyGeoBox(m_geoBox->sticky()); 0257 Options::setShowGeoBox(m_geoBox->isVisibleTo(m_iboxes)); 0258 // Obj box 0259 Options::setPositionFocusBox(m_objBox->pos()); 0260 Options::setShadeFocusBox(m_objBox->shaded()); 0261 Options::setStickyFocusBox(m_objBox->sticky()); 0262 Options::setShowFocusBox(m_objBox->isVisibleTo(m_iboxes)); 0263 0264 //store focus values in Options 0265 //If not tracking and using Alt/Az coords, stor the Alt/Az coordinates 0266 if (Options::useAltAz() && !Options::isTracking()) 0267 { 0268 Options::setFocusRA(focus()->az().Degrees()); 0269 Options::setFocusDec(focus()->alt().Degrees()); 0270 } 0271 else 0272 { 0273 Options::setFocusRA(focus()->ra().Hours()); 0274 Options::setFocusDec(focus()->dec().Degrees()); 0275 } 0276 0277 #ifdef HAVE_OPENGL 0278 delete m_SkyMapGLDraw; 0279 delete m_SkyMapQDraw; 0280 m_SkyMapDraw = 0; // Just a formality 0281 #else 0282 delete m_SkyMapDraw; 0283 #endif 0284 0285 delete pmenu; 0286 0287 delete m_proj; 0288 0289 pinstance = nullptr; 0290 } 0291 0292 void SkyMap::showFocusCoords() 0293 { 0294 if (focusObject() && Options::isTracking()) 0295 emit objectChanged(focusObject()); 0296 else 0297 emit positionChanged(focus()); 0298 } 0299 0300 void SkyMap::updateInfoBoxes() 0301 { 0302 if (focusObject() && Options::isTracking()) 0303 m_objBox->slotObjectChanged(focusObject()); 0304 else 0305 m_objBox->slotPointChanged(focus()); 0306 } 0307 0308 void SkyMap::slotTransientLabel() 0309 { 0310 //This function is only called if the HoverTimer manages to timeout. 0311 //(HoverTimer is restarted with every mouseMoveEvent; so if it times 0312 //out, that means there was no mouse movement for HOVER_INTERVAL msec.) 0313 if (hasFocus() && !slewing && 0314 !(Options::useAltAz() && Options::showGround() && m_MousePoint.altRefracted().Degrees() < 0.0)) 0315 { 0316 double maxrad = 1000.0 / Options::zoomFactor(); 0317 SkyObject *so = data->skyComposite()->objectNearest(&m_MousePoint, maxrad); 0318 0319 if (so && !isObjectLabeled(so)) 0320 { 0321 QString name = so->translatedLongName(); 0322 if (!std::isnan(so->mag())) 0323 name += QString(": %1<sup>m</sup>").arg(QString::number(so->mag(), 'f', 1)); 0324 QToolTip::showText(QCursor::pos(), name, this); 0325 } 0326 } 0327 } 0328 0329 //Slots 0330 0331 void SkyMap::setClickedObject(SkyObject *o) 0332 { 0333 ClickedObject = o; 0334 } 0335 0336 void SkyMap::setFocusObject(SkyObject *o) 0337 { 0338 FocusObject = o; 0339 if (FocusObject) 0340 Options::setFocusObject(FocusObject->name()); 0341 else 0342 Options::setFocusObject(i18n("nothing")); 0343 } 0344 0345 void SkyMap::slotCenter() 0346 { 0347 KStars *kstars = KStars::Instance(); 0348 TrailObject *trailObj = dynamic_cast<TrailObject *>(focusObject()); 0349 0350 SkyPoint *foc; 0351 if(ClickedObject != nullptr) 0352 foc = ClickedObject; 0353 else 0354 foc = &ClickedPoint; 0355 0356 //clear the planet trail of old focusObject, if it was temporary 0357 if (trailObj && data->temporaryTrail) 0358 { 0359 trailObj->clearTrail(); 0360 data->temporaryTrail = false; 0361 } 0362 0363 //If the requested object is below the opaque horizon, issue a warning message 0364 //(unless user is already pointed below the horizon) 0365 if (Options::useAltAz() && Options::showGround() && 0366 focus()->alt().Degrees() > SkyPoint::altCrit && 0367 foc->alt().Degrees() <= SkyPoint::altCrit) 0368 { 0369 QString caption = i18n("Requested Position Below Horizon"); 0370 QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?"); 0371 if (KMessageBox::warningYesNo(this, message, caption, KGuiItem(i18n("Go Anyway")), 0372 KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz") == KMessageBox::No) 0373 { 0374 setClickedObject(nullptr); 0375 setFocusObject(nullptr); 0376 Options::setIsTracking(false); 0377 0378 return; 0379 } 0380 } 0381 0382 //set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset 0383 //destination to previous object... 0384 setFocusObject(ClickedObject); 0385 if(ClickedObject == nullptr) 0386 setFocusPoint(&ClickedPoint); 0387 0388 Options::setIsTracking(true); 0389 0390 if (kstars) 0391 { 0392 kstars->actionCollection() 0393 ->action("track_object") 0394 ->setIcon(QIcon::fromTheme("document-encrypt")); 0395 kstars->actionCollection()->action("track_object")->setText(i18n("Stop &Tracking")); 0396 } 0397 0398 //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail 0399 0400 if (Options::useAutoTrail() && trailObj && trailObj->hasTrail()) 0401 { 0402 trailObj->addToTrail(); 0403 data->temporaryTrail = true; 0404 } 0405 0406 //update the destination to the selected coordinates 0407 if (Options::useAltAz()) 0408 { 0409 setDestinationAltAz(foc->alt(), foc->az(), false); 0410 } 0411 else 0412 { 0413 setDestination(*foc); 0414 } 0415 0416 foc->EquatorialToHorizontal(data->lst(), data->geo()->lat()); 0417 0418 //display coordinates in statusBar 0419 emit mousePointChanged(foc); 0420 showFocusCoords(); //update FocusBox 0421 } 0422 0423 void SkyMap::slotUpdateSky(bool now) 0424 { 0425 // Code moved from KStarsData::updateTime() 0426 //Update focus 0427 updateFocus(); 0428 0429 if (now) 0430 QTimer::singleShot( 0431 0, this, 0432 SLOT(forceUpdateNow())); // Why is it done this way rather than just calling forceUpdateNow()? -- asimha // --> Opening a neww thread? -- Valentin 0433 else 0434 forceUpdate(); 0435 } 0436 0437 void SkyMap::slotDSS() 0438 { 0439 dms ra(0.0), dec(0.0); 0440 QString urlstring; 0441 0442 //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords 0443 //if we clicked on empty sky, we need to precess to J2000. 0444 if (clickedObject()) 0445 { 0446 urlstring = KSDssDownloader::getDSSURL(clickedObject()); 0447 } 0448 else 0449 { 0450 //SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); 0451 SkyPoint deprecessedPoint = clickedPoint()->catalogueCoord(data->updateNum()->julianDay()); 0452 ra = deprecessedPoint.ra(); 0453 dec = deprecessedPoint.dec(); 0454 urlstring = KSDssDownloader::getDSSURL(ra, dec); // Use default size for non-objects 0455 } 0456 0457 QUrl url(urlstring); 0458 0459 KStars *kstars = KStars::Instance(); 0460 if (kstars) 0461 { 0462 new ImageViewer( 0463 url, i18n("Digitized Sky Survey image provided by the Space Telescope Science Institute [free for non-commercial use]."), 0464 this); 0465 //iv->show(); 0466 } 0467 } 0468 0469 void SkyMap::slotCopyCoordinates() 0470 { 0471 dms J2000RA(0.0), J2000DE(0.0), JNowRA(0.0), JNowDE(0.0), Az, Alt; 0472 if (clickedObject()) 0473 { 0474 J2000RA = clickedObject()->ra0(); 0475 J2000DE = clickedObject()->dec0(); 0476 JNowRA = clickedObject()->ra(); 0477 JNowDE = clickedObject()->dec(); 0478 Az = clickedObject()->az(); 0479 Alt = clickedObject()->alt(); 0480 } 0481 else 0482 { 0483 // Empty point only have valid JNow RA/DE, not J2000 information. 0484 SkyPoint emptyPoint = *clickedPoint(); 0485 // Now get J2000 from JNow but de-aberrating, de-nutating, de-preccessing 0486 // This modifies emptyPoint, but the RA/DE are now missing and need 0487 // to be repopulated. 0488 emptyPoint.catalogueCoord(data->updateNum()->julianDay()); 0489 emptyPoint.setRA(clickedPoint()->ra()); 0490 emptyPoint.setDec(clickedPoint()->dec()); 0491 emptyPoint.EquatorialToHorizontal(data->lst(), data->geo()->lat()); 0492 0493 J2000RA = emptyPoint.ra0(); 0494 J2000DE = emptyPoint.dec0(); 0495 JNowRA = emptyPoint.ra(); 0496 JNowDE = emptyPoint.dec(); 0497 Az = emptyPoint.az(); 0498 Alt = emptyPoint.alt(); 0499 } 0500 0501 QApplication::clipboard()->setText(i18nc("Equatorial & Horizontal Coordinates", 0502 "JNow:\t%1\t%2\nJ2000:\t%3\t%4\nAzAlt:\t%5\t%6", 0503 JNowRA.toHMSString(), 0504 JNowDE.toDMSString(), 0505 J2000RA.toHMSString(), 0506 J2000DE.toDMSString(), 0507 Az.toDMSString(), 0508 Alt.toDMSString())); 0509 } 0510 0511 0512 void SkyMap::slotCopyTLE() 0513 { 0514 0515 QString tle = ""; 0516 if (clickedObject()->type() == SkyObject::SATELLITE) 0517 { 0518 Satellite *sat = (Satellite *) clickedObject(); 0519 tle = sat->tle(); 0520 } 0521 else 0522 { 0523 tle = "NO TLE FOR OBJECT"; 0524 } 0525 0526 0527 QApplication::clipboard()->setText(tle); 0528 } 0529 0530 void SkyMap::slotSDSS() 0531 { 0532 // TODO: Remove code duplication -- we have the same stuff 0533 // implemented in ObservingList::setCurrentImage() etc. in 0534 // tools/observinglist.cpp; must try to de-duplicate as much as 0535 // possible. 0536 QString URLprefix("http://skyserver.sdss.org/dr16/SkyServerWS/ImgCutout/getjpeg?"); 0537 QString URLsuffix("&scale=1.0&width=600&height=600"); 0538 dms ra(0.0), dec(0.0); 0539 QString RAString, DecString; 0540 0541 //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords 0542 //if we clicked on empty sky, we need to precess to J2000. 0543 if (clickedObject()) 0544 { 0545 ra = clickedObject()->ra0(); 0546 dec = clickedObject()->dec0(); 0547 } 0548 else 0549 { 0550 //SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); 0551 SkyPoint deprecessedPoint = clickedPoint()->catalogueCoord(data->updateNum()->julianDay()); 0552 deprecessedPoint.catalogueCoord(data->updateNum()->julianDay()); 0553 ra = deprecessedPoint.ra(); 0554 dec = deprecessedPoint.dec(); 0555 } 0556 0557 RAString = QString::asprintf("ra=%f", ra.Degrees()); 0558 DecString = QString::asprintf("&dec=%f", dec.Degrees()); 0559 0560 //concat all the segments into the kview command line: 0561 QUrl url(URLprefix + RAString + DecString + URLsuffix); 0562 0563 KStars *kstars = KStars::Instance(); 0564 if (kstars) 0565 { 0566 new ImageViewer(url, 0567 i18n("Sloan Digital Sky Survey image provided by the Astrophysical Research Consortium [free " 0568 "for non-commercial use]."), 0569 this); 0570 //iv->show(); 0571 } 0572 } 0573 0574 void SkyMap::slotEyepieceView() 0575 { 0576 KStars::Instance()->slotEyepieceView((clickedObject() ? clickedObject() : clickedPoint())); 0577 } 0578 void SkyMap::slotBeginAngularDistance() 0579 { 0580 beginRulerMode(false); 0581 } 0582 0583 void SkyMap::slotBeginStarHop() 0584 { 0585 beginRulerMode(true); 0586 } 0587 0588 void SkyMap::beginRulerMode(bool starHopRuler) 0589 { 0590 rulerMode = true; 0591 starHopDefineMode = starHopRuler; 0592 AngularRuler.clear(); 0593 0594 //If the cursor is near a SkyObject, reset the AngularRuler's 0595 //start point to the position of the SkyObject 0596 double maxrad = 1000.0 / Options::zoomFactor(); 0597 SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); 0598 if (so) 0599 { 0600 AngularRuler.append(so); 0601 AngularRuler.append(so); 0602 m_rulerStartPoint = so; 0603 } 0604 else 0605 { 0606 AngularRuler.append(clickedPoint()); 0607 AngularRuler.append(clickedPoint()); 0608 m_rulerStartPoint = clickedPoint(); 0609 } 0610 0611 AngularRuler.update(data); 0612 } 0613 0614 void SkyMap::slotEndRulerMode() 0615 { 0616 if (!rulerMode) 0617 return; 0618 if (!starHopDefineMode) // Angular Ruler 0619 { 0620 QString sbMessage; 0621 0622 //If the cursor is near a SkyObject, reset the AngularRuler's 0623 //end point to the position of the SkyObject 0624 double maxrad = 1000.0 / Options::zoomFactor(); 0625 SkyPoint *rulerEndPoint; 0626 SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); 0627 if (so) 0628 { 0629 AngularRuler.setPoint(1, so); 0630 sbMessage = so->translatedLongName() + " "; 0631 rulerEndPoint = so; 0632 } 0633 else 0634 { 0635 AngularRuler.setPoint(1, clickedPoint()); 0636 rulerEndPoint = clickedPoint(); 0637 } 0638 0639 rulerMode = false; 0640 AngularRuler.update(data); 0641 dms angularDistance = AngularRuler.angularSize(); 0642 0643 sbMessage += i18n("Angular distance: %1", angularDistance.toDMSString()); 0644 0645 const StarObject *p1 = dynamic_cast<const StarObject *>(m_rulerStartPoint); 0646 const StarObject *p2 = dynamic_cast<const StarObject *>(rulerEndPoint); 0647 0648 qCDebug(KSTARS) << "Starobjects? " << p1 << p2; 0649 if (p1 && p2) 0650 qCDebug(KSTARS) << "Distances: " << p1->distance() << "pc; " << p2->distance() << "pc"; 0651 if (p1 && p2 && std::isfinite(p1->distance()) && std::isfinite(p2->distance()) && p1->distance() > 0 && 0652 p2->distance() > 0) 0653 { 0654 double dist = sqrt(p1->distance() * p1->distance() + p2->distance() * p2->distance() - 0655 2 * p1->distance() * p2->distance() * cos(angularDistance.radians())); 0656 qCDebug(KSTARS) << "Could calculate physical distance: " << dist << " pc"; 0657 sbMessage += i18n("; Physical distance: %1 pc", QString::number(dist)); 0658 } 0659 0660 AngularRuler.clear(); 0661 0662 // Create unobsructive message box with suicidal tendencies 0663 // to display result. 0664 InfoBoxWidget *box = new InfoBoxWidget(true, mapFromGlobal(QCursor::pos()), 0, QStringList(sbMessage), this); 0665 connect(box, SIGNAL(clicked()), box, SLOT(deleteLater())); 0666 QTimer::singleShot(5000, box, SLOT(deleteLater())); 0667 box->adjust(); 0668 box->show(); 0669 } 0670 else // Star Hop 0671 { 0672 StarHopperDialog *shd = new StarHopperDialog(this); 0673 const SkyPoint &startHop = *AngularRuler.point(0); 0674 const SkyPoint &stopHop = *clickedPoint(); 0675 double fov; // Field of view in arcminutes 0676 bool ok; // true if user did not cancel the operation 0677 if (data->getAvailableFOVs().size() == 1) 0678 { 0679 // Exactly 1 FOV symbol visible, so use that. Also assume a circular FOV of size min{sizeX, sizeY} 0680 FOV *f = data->getAvailableFOVs().first(); 0681 fov = ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX()); 0682 ok = true; 0683 } 0684 else if (!data->getAvailableFOVs().isEmpty()) 0685 { 0686 // Ask the user to choose from a list of available FOVs. 0687 FOV const *f; 0688 QMap<QString, double> nameToFovMap; 0689 foreach (f, data->getAvailableFOVs()) 0690 { 0691 nameToFovMap.insert(f->name(), 0692 ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX())); 0693 } 0694 fov = nameToFovMap[QInputDialog::getItem(this, i18n("Star Hopper: Choose a field-of-view"), 0695 i18n("FOV to use for star hopping:"), nameToFovMap.keys(), 0, 0696 false, &ok)]; 0697 } 0698 else 0699 { 0700 // Ask the user to enter a field of view 0701 fov = 0702 QInputDialog::getDouble(this, i18n("Star Hopper: Enter field-of-view to use"), 0703 i18n("FOV to use for star hopping (in arcminutes):"), 60.0, 1.0, 600.0, 1, &ok); 0704 } 0705 0706 Q_ASSERT(fov > 0.0); 0707 0708 if (ok) 0709 { 0710 qCDebug(KSTARS) << "fov = " << fov; 0711 0712 shd->starHop(startHop, stopHop, fov / 60.0, 9.0); //FIXME: Hardcoded maglimit value 0713 shd->show(); 0714 } 0715 0716 rulerMode = false; 0717 } 0718 } 0719 0720 void SkyMap::slotCancelRulerMode(void) 0721 { 0722 rulerMode = false; 0723 AngularRuler.clear(); 0724 } 0725 0726 void SkyMap::slotAddFlag() 0727 { 0728 KStars *ks = KStars::Instance(); 0729 0730 // popup FlagManager window and update coordinates 0731 ks->slotFlagManager(); 0732 ks->flagManager()->clearFields(); 0733 0734 //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords 0735 //if we clicked on empty sky, we need to precess to J2000. 0736 0737 dms J2000RA, J2000DE; 0738 0739 if (clickedObject()) 0740 { 0741 J2000RA = clickedObject()->ra0(); 0742 J2000DE = clickedObject()->dec0(); 0743 } 0744 else 0745 { 0746 //SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); 0747 SkyPoint deprecessedPoint = clickedPoint()->catalogueCoord(data->updateNum()->julianDay()); 0748 deprecessedPoint.catalogueCoord(data->updateNum()->julianDay()); 0749 J2000RA = deprecessedPoint.ra(); 0750 J2000DE = deprecessedPoint.dec(); 0751 } 0752 0753 ks->flagManager()->setRaDec(J2000RA, J2000DE); 0754 } 0755 0756 void SkyMap::slotEditFlag(int flagIdx) 0757 { 0758 KStars *ks = KStars::Instance(); 0759 0760 // popup FlagManager window and switch to selected flag 0761 ks->slotFlagManager(); 0762 ks->flagManager()->showFlag(flagIdx); 0763 } 0764 0765 void SkyMap::slotDeleteFlag(int flagIdx) 0766 { 0767 KStars *ks = KStars::Instance(); 0768 0769 ks->data()->skyComposite()->flags()->remove(flagIdx); 0770 ks->data()->skyComposite()->flags()->saveToFile(); 0771 0772 // if there is FlagManager created, update its flag model 0773 if (ks->flagManager()) 0774 { 0775 ks->flagManager()->deleteFlagItem(flagIdx); 0776 } 0777 } 0778 0779 void SkyMap::slotImage() 0780 { 0781 const auto *action = qobject_cast<QAction *>(sender()); 0782 const auto url = action->data().toUrl(); 0783 const QString message{ action->text().remove('&') }; 0784 0785 if (!url.isEmpty()) 0786 new ImageViewer(url, clickedObject()->messageFromTitle(message), this); 0787 } 0788 0789 void SkyMap::slotInfo() 0790 { 0791 const auto *action = qobject_cast<QAction *>(sender()); 0792 const auto url = action->data().toUrl(); 0793 0794 if (!url.isEmpty()) 0795 QDesktopServices::openUrl(url); 0796 } 0797 0798 bool SkyMap::isObjectLabeled(SkyObject *object) 0799 { 0800 return data->skyComposite()->labelObjects().contains(object); 0801 } 0802 0803 SkyPoint SkyMap::getCenterPoint() 0804 { 0805 SkyPoint retVal; 0806 // FIXME: subtraction of these 0.00001 is a simple workaround, because wrong 0807 // SkyPoint is returned when _exact_ center of SkyMap is passed to the projector. 0808 retVal = projector()->fromScreen(QPointF((qreal)width() / 2 - 0.00001, (qreal)height() / 2 - 0.00001), data->lst(), 0809 data->geo()->lat()); 0810 return retVal; 0811 } 0812 0813 void SkyMap::slotRemoveObjectLabel() 0814 { 0815 data->skyComposite()->removeNameLabel(clickedObject()); 0816 forceUpdate(); 0817 } 0818 0819 void SkyMap::slotRemoveCustomObject() 0820 { 0821 auto *object = dynamic_cast<CatalogObject *>(clickedObject()); 0822 if (!object) 0823 return; 0824 0825 const auto &cat = object->getCatalog(); 0826 if (!cat.mut) 0827 return; 0828 0829 CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() }; 0830 manager.remove_object(cat.id, object->getObjectId()); 0831 0832 emit removeSkyObject(object); 0833 data->skyComposite()->removeFromNames(object); 0834 data->skyComposite()->removeFromLists(object); 0835 data->skyComposite()->reloadDeepSky(); 0836 KStars::Instance()->updateTime(); 0837 } 0838 0839 void SkyMap::slotAddObjectLabel() 0840 { 0841 data->skyComposite()->addNameLabel(clickedObject()); 0842 forceUpdate(); 0843 } 0844 0845 void SkyMap::slotRemovePlanetTrail() 0846 { 0847 TrailObject *tobj = dynamic_cast<TrailObject *>(clickedObject()); 0848 if (tobj) 0849 { 0850 tobj->clearTrail(); 0851 forceUpdate(); 0852 } 0853 } 0854 0855 void SkyMap::slotAddPlanetTrail() 0856 { 0857 TrailObject *tobj = dynamic_cast<TrailObject *>(clickedObject()); 0858 if (tobj) 0859 { 0860 tobj->addToTrail(); 0861 forceUpdate(); 0862 } 0863 } 0864 0865 void SkyMap::slotDetail() 0866 { 0867 // check if object is selected 0868 if (!clickedObject()) 0869 { 0870 KMessageBox::sorry(this, i18n("No object selected."), i18n("Object Details")); 0871 return; 0872 } 0873 DetailDialog *detail = new DetailDialog(clickedObject(), data->ut(), data->geo(), KStars::Instance()); 0874 detail->setAttribute(Qt::WA_DeleteOnClose); 0875 detail->show(); 0876 } 0877 0878 void SkyMap::slotObjectSelected() 0879 { 0880 if (m_objPointingMode && KStars::Instance()->printingWizard()) 0881 { 0882 KStars::Instance()->printingWizard()->pointingDone(clickedObject()); 0883 m_objPointingMode = false; 0884 } 0885 } 0886 0887 void SkyMap::slotCancelLegendPreviewMode() 0888 { 0889 m_previewLegend = false; 0890 forceUpdate(true); 0891 KStars::Instance()->showImgExportDialog(); 0892 } 0893 0894 void SkyMap::slotFinishFovCaptureMode() 0895 { 0896 if (m_fovCaptureMode && KStars::Instance()->printingWizard()) 0897 { 0898 KStars::Instance()->printingWizard()->fovCaptureDone(); 0899 m_fovCaptureMode = false; 0900 } 0901 } 0902 0903 void SkyMap::slotCaptureFov() 0904 { 0905 if (KStars::Instance()->printingWizard()) 0906 { 0907 KStars::Instance()->printingWizard()->captureFov(); 0908 } 0909 } 0910 0911 void SkyMap::slotClockSlewing() 0912 { 0913 //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock. 0914 if ((fabs(data->clock()->scale()) > Options::slewTimeScale()) ^ clockSlewing) 0915 { 0916 data->clock()->setManualMode(!clockSlewing); 0917 clockSlewing = !clockSlewing; 0918 // don't change automatically the DST status 0919 KStars *kstars = KStars::Instance(); 0920 if (kstars) 0921 kstars->updateTime(false); 0922 } 0923 } 0924 0925 void SkyMap::setFocus(SkyPoint *p) 0926 { 0927 setFocus(p->ra(), p->dec()); 0928 } 0929 0930 void SkyMap::setFocus(const dms &ra, const dms &dec) 0931 { 0932 Options::setFocusRA(ra.Hours()); 0933 Options::setFocusDec(dec.Degrees()); 0934 0935 focus()->set(ra, dec); 0936 focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); 0937 } 0938 0939 void SkyMap::setFocusAltAz(const dms &alt, const dms &az) 0940 { 0941 Options::setFocusRA(focus()->ra().Hours()); 0942 Options::setFocusDec(focus()->dec().Degrees()); 0943 focus()->setAlt(alt); 0944 focus()->setAz(az); 0945 focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); 0946 0947 slewing = false; 0948 forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work. 0949 } 0950 0951 void SkyMap::setDestination(const SkyPoint &p) 0952 { 0953 setDestination(p.ra(), p.dec()); 0954 } 0955 0956 void SkyMap::setDestination(const dms &ra, const dms &dec) 0957 { 0958 destination()->set(ra, dec); 0959 destination()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); 0960 emit destinationChanged(); 0961 } 0962 0963 void SkyMap::setDestinationAltAz(const dms &alt, const dms &az, bool altIsRefracted) 0964 { 0965 if (altIsRefracted) 0966 { 0967 // The alt in the SkyPoint is always actual, not apparent 0968 destination()->setAlt(SkyPoint::unrefract(alt)); 0969 } 0970 else 0971 { 0972 destination()->setAlt(alt); 0973 } 0974 destination()->setAz(az); 0975 destination()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); 0976 emit destinationChanged(); 0977 } 0978 0979 void SkyMap::setClickedPoint(const SkyPoint *f) 0980 { 0981 ClickedPoint = *f; 0982 } 0983 0984 void SkyMap::updateFocus() 0985 { 0986 if (slewing) 0987 return; 0988 0989 //Tracking on an object 0990 if (Options::isTracking()) 0991 { 0992 if (focusObject()) 0993 { 0994 focusObject()->updateCoordsNow(data->updateNum()); 0995 setDestination(*focusObject()); 0996 } 0997 else 0998 setDestination(*focusPoint()); 0999 } 1000 else 1001 focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); 1002 } 1003 1004 void SkyMap::slewFocus() 1005 { 1006 //Don't slew if the mouse button is pressed 1007 //Also, no animated slews if the Manual Clock is active 1008 //08/2002: added possibility for one-time skipping of slew with snapNextFocus 1009 if (!mouseButtonDown) 1010 { 1011 bool goSlew = (Options::useAnimatedSlewing() && !data->snapNextFocus()) && 1012 !(data->clock()->isManualMode() && data->clock()->isActive()); 1013 if (goSlew) 1014 { 1015 double dX, dY; 1016 double maxstep = 10.0; 1017 if (Options::useAltAz()) 1018 { 1019 dX = destination()->az().Degrees() - focus()->az().Degrees(); 1020 dY = destination()->alt().Degrees() - focus()->alt().Degrees(); 1021 } 1022 else 1023 { 1024 dX = destination()->ra().Degrees() - focus()->ra().Degrees(); 1025 dY = destination()->dec().Degrees() - focus()->dec().Degrees(); 1026 } 1027 1028 //switch directions to go the short way around the celestial sphere, if necessary. 1029 dX = KSUtils::reduceAngle(dX, -180.0, 180.0); 1030 1031 double r0 = sqrt(dX * dX + dY * dY); 1032 if (r0 < 20.0) //smaller slews have smaller maxstep 1033 { 1034 maxstep *= (10.0 + 0.5 * r0) / 20.0; 1035 } 1036 double step = 0.5; 1037 double r = r0; 1038 while (r > step) 1039 { 1040 //DEBUG 1041 //qDebug() << Q_FUNC_INFO << step << ": " << r << ": " << r0; 1042 double fX = dX / r; 1043 double fY = dY / r; 1044 1045 if (Options::useAltAz()) 1046 { 1047 focus()->setAlt(focus()->alt().Degrees() + fY * step); 1048 focus()->setAz(dms(focus()->az().Degrees() + fX * step).reduce()); 1049 focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); 1050 } 1051 else 1052 { 1053 fX = fX / 15.; //convert RA degrees to hours 1054 SkyPoint newFocus(focus()->ra().Hours() + fX * step, focus()->dec().Degrees() + fY * step); 1055 setFocus(&newFocus); 1056 focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); 1057 } 1058 1059 slewing = true; 1060 1061 forceUpdate(); 1062 qApp->processEvents(); //keep up with other stuff 1063 1064 if (Options::useAltAz()) 1065 { 1066 dX = destination()->az().Degrees() - focus()->az().Degrees(); 1067 dY = destination()->alt().Degrees() - focus()->alt().Degrees(); 1068 } 1069 else 1070 { 1071 dX = destination()->ra().Degrees() - focus()->ra().Degrees(); 1072 dY = destination()->dec().Degrees() - focus()->dec().Degrees(); 1073 } 1074 1075 //switch directions to go the short way around the celestial sphere, if necessary. 1076 dX = KSUtils::reduceAngle(dX, -180.0, 180.0); 1077 r = sqrt(dX * dX + dY * dY); 1078 1079 //Modify step according to a cosine-shaped profile 1080 //centered on the midpoint of the slew 1081 //NOTE: don't allow the full range from -PI/2 to PI/2 1082 //because the slew will never reach the destination as 1083 //the speed approaches zero at the end! 1084 double t = dms::PI * (r - 0.5 * r0) / (1.05 * r0); 1085 step = cos(t) * maxstep; 1086 } 1087 } 1088 1089 //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination 1090 //set focus=destination. 1091 if (Options::useAltAz()) 1092 { 1093 setFocusAltAz(destination()->alt(), destination()->az()); 1094 focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); 1095 } 1096 else 1097 { 1098 setFocus(destination()); 1099 focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); 1100 } 1101 1102 slewing = false; 1103 1104 //Turn off snapNextFocus, we only want it to happen once 1105 if (data->snapNextFocus()) 1106 { 1107 data->setSnapNextFocus(false); 1108 } 1109 1110 //Start the HoverTimer. if the user leaves the mouse in place after a slew, 1111 //we want to attach a label to the nearest object. 1112 if (Options::useHoverLabel()) 1113 m_HoverTimer.start(HOVER_INTERVAL); 1114 1115 forceUpdate(); 1116 } 1117 } 1118 1119 void SkyMap::slotZoomIn() 1120 { 1121 setZoomFactor(Options::zoomFactor() * DZOOM); 1122 } 1123 1124 void SkyMap::slotZoomOut() 1125 { 1126 setZoomFactor(Options::zoomFactor() / DZOOM); 1127 } 1128 1129 void SkyMap::slotZoomDefault() 1130 { 1131 setZoomFactor(DEFAULTZOOM); 1132 } 1133 1134 void SkyMap::setZoomFactor(double factor) 1135 { 1136 Options::setZoomFactor(KSUtils::clamp(factor, MINZOOM, MAXZOOM)); 1137 forceUpdate(); 1138 emit zoomChanged(); 1139 } 1140 1141 // force a new calculation of the skymap (used instead of update(), which may skip the redraw) 1142 // if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue 1143 // also, determine new coordinates of mouse cursor. 1144 void SkyMap::forceUpdate(bool now) 1145 { 1146 QPoint mp(mapFromGlobal(QCursor::pos())); 1147 if (!projector()->unusablePoint(mp)) 1148 { 1149 //determine RA, Dec of mouse pointer 1150 m_MousePoint = projector()->fromScreen(mp, data->lst(), data->geo()->lat()); 1151 } 1152 1153 computeSkymap = true; 1154 1155 // Ensure that stars are recomputed 1156 data->incUpdateID(); 1157 1158 if (now) 1159 m_SkyMapDraw->repaint(); 1160 else 1161 m_SkyMapDraw->update(); 1162 } 1163 1164 float SkyMap::fov() 1165 { 1166 float diagonalPixels = sqrt(static_cast<double>(width() * width() + height() * height())); 1167 return diagonalPixels / (2 * Options::zoomFactor() * dms::DegToRad); 1168 } 1169 1170 void SkyMap::setupProjector() 1171 { 1172 //Update View Parameters for projection 1173 ViewParams p; 1174 p.focus = focus(); 1175 p.height = height(); 1176 p.width = width(); 1177 p.useAltAz = Options::useAltAz(); 1178 p.useRefraction = Options::useRefraction(); 1179 p.zoomFactor = Options::zoomFactor(); 1180 p.fillGround = Options::showGround(); 1181 //Check if we need a new projector 1182 if (m_proj && Options::projection() == m_proj->type()) 1183 m_proj->setViewParams(p); 1184 else 1185 { 1186 delete m_proj; 1187 switch (Options::projection()) 1188 { 1189 case Gnomonic: 1190 m_proj = new GnomonicProjector(p); 1191 break; 1192 case Stereographic: 1193 m_proj = new StereographicProjector(p); 1194 break; 1195 case Orthographic: 1196 m_proj = new OrthographicProjector(p); 1197 break; 1198 case AzimuthalEquidistant: 1199 m_proj = new AzimuthalEquidistantProjector(p); 1200 break; 1201 case Equirectangular: 1202 m_proj = new EquirectangularProjector(p); 1203 break; 1204 case Lambert: 1205 default: 1206 //TODO: implement other projection classes 1207 m_proj = new LambertProjector(p); 1208 break; 1209 } 1210 } 1211 } 1212 1213 void SkyMap::setZoomMouseCursor() 1214 { 1215 mouseMoveCursor = false; // no mousemove cursor 1216 mouseDragCursor = false; 1217 QBitmap cursor = zoomCursorBitmap(2); 1218 QBitmap mask = zoomCursorBitmap(4); 1219 setCursor(QCursor(cursor, mask)); 1220 } 1221 1222 void SkyMap::setMouseCursorShape(Cursor type) 1223 { 1224 // no mousemove cursor 1225 mouseMoveCursor = false; 1226 mouseDragCursor = false; 1227 1228 switch (type) 1229 { 1230 case Cross: 1231 { 1232 QBitmap cursor = defaultCursorBitmap(2); 1233 QBitmap mask = defaultCursorBitmap(3); 1234 setCursor(QCursor(cursor, mask)); 1235 } 1236 break; 1237 1238 case Circle: 1239 { 1240 QBitmap cursor = circleCursorBitmap(2); 1241 QBitmap mask = circleCursorBitmap(3); 1242 setCursor(QCursor(cursor, mask)); 1243 } 1244 break; 1245 1246 case NoCursor: 1247 setCursor(Qt::ArrowCursor); 1248 break; 1249 } 1250 } 1251 1252 void SkyMap::setMouseMoveCursor() 1253 { 1254 if (mouseButtonDown) 1255 { 1256 setCursor(Qt::SizeAllCursor); // cursor shape defined in qt 1257 mouseMoveCursor = true; 1258 } 1259 } 1260 1261 void SkyMap::setMouseDragCursor() 1262 { 1263 if (mouseButtonDown) 1264 { 1265 setCursor(Qt::OpenHandCursor); // cursor shape defined in qt 1266 mouseDragCursor = true; 1267 } 1268 } 1269 1270 void SkyMap::updateAngleRuler() 1271 { 1272 if (rulerMode && (!pmenu || !pmenu->isVisible())) 1273 AngularRuler.setPoint(1, &m_MousePoint); 1274 AngularRuler.update(data); 1275 } 1276 1277 bool SkyMap::isSlewing() const 1278 { 1279 return (slewing || (clockSlewing && data->clock()->isActive())); 1280 } 1281 1282 void SkyMap::slotStartXplanetViewer() 1283 { 1284 if(clickedObject()) 1285 new XPlanetImageViewer(clickedObject()->name(), this); 1286 else 1287 new XPlanetImageViewer(i18n("Saturn"), this); 1288 } 1289 1290