diff --git a/kstars/skymap.cpp b/kstars/skymap.cpp index b43956946..f7236657d 100644 --- a/kstars/skymap.cpp +++ b/kstars/skymap.cpp @@ -1,1327 +1,1327 @@ /************************************************************************** skymap.cpp - K Desktop Planetarium ------------------- begin : Sat Feb 10 2001 copyright : (C) 2001 by Jason Harris email : jharris@30doradus.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifdef _WIN32 #include #endif #include "skymap.h" #include "ksasteroid.h" #include "kstars_debug.h" #include "fov.h" #include "imageviewer.h" #include "xplanetimageviewer.h" #include "ksdssdownloader.h" #include "kspaths.h" #include "kspopupmenu.h" #include "kstars.h" #include "ksutils.h" #include "Options.h" #include "skymapcomposite.h" #ifdef HAVE_OPENGL #include "skymapgldraw.h" #endif #include "skymapqdraw.h" #include "starhopperdialog.h" #include "starobject.h" #include "syncedcatalogcomponent.h" #include "texturemanager.h" #include "dialogs/detaildialog.h" #include "printing/printingwizard.h" #include "skycomponents/flagcomponent.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksplanetbase.h" #include "tools/flagmanager.h" #include "widgets/infoboxwidget.h" #include "projections/azimuthalequidistantprojector.h" #include "projections/equirectangularprojector.h" #include "projections/lambertprojector.h" #include "projections/gnomonicprojector.h" #include "projections/orthographicprojector.h" #include "projections/stereographicprojector.h" #include #include #include #include #include #include #include #include #include #include #include namespace { // Draw bitmap for zoom cursor. Width is size of pen to draw with. QBitmap zoomCursorBitmap(int width) { QBitmap b(32, 32); b.fill(Qt::color0); int mx = 16, my = 16; // Begin drawing QPainter p; p.begin(&b); p.setPen(QPen(Qt::color1, width)); p.drawEllipse(mx - 7, my - 7, 14, 14); p.drawLine(mx + 5, my + 5, mx + 11, my + 11); p.end(); return b; } // Draw bitmap for default cursor. Width is size of pen to draw with. QBitmap defaultCursorBitmap(int width) { QBitmap b(32, 32); b.fill(Qt::color0); int mx = 16, my = 16; // Begin drawing QPainter p; p.begin(&b); p.setPen(QPen(Qt::color1, width)); // 1. diagonal p.drawLine(mx - 2, my - 2, mx - 8, mx - 8); p.drawLine(mx + 2, my + 2, mx + 8, mx + 8); // 2. diagonal p.drawLine(mx - 2, my + 2, mx - 8, mx + 8); p.drawLine(mx + 2, my - 2, mx + 8, mx - 8); p.end(); return b; } QBitmap circleCursorBitmap(int width) { QBitmap b(32, 32); b.fill(Qt::color0); int mx = 16, my = 16; // Begin drawing QPainter p; p.begin(&b); p.setPen(QPen(Qt::color1, width)); // Circle p.drawEllipse(mx - 8, my - 8, mx, my); // 1. diagonal p.drawLine(mx - 8, my - 8, 0, 0); p.drawLine(mx + 8, my - 8, 32, 0); // 2. diagonal p.drawLine(mx - 8, my + 8, 0, 32); p.drawLine(mx + 8, my + 8, 32, 32); p.end(); return b; } } SkyMap *SkyMap::pinstance = nullptr; SkyMap *SkyMap::Create() { delete pinstance; pinstance = new SkyMap(); return pinstance; } SkyMap *SkyMap::Instance() { return pinstance; } SkyMap::SkyMap() : QGraphicsView(KStars::Instance()), computeSkymap(true), rulerMode(false), data(KStarsData::Instance()), pmenu(nullptr), ClickedObject(nullptr), FocusObject(nullptr), m_proj(nullptr), m_previewLegend(false), m_objPointingMode(false) { #if !defined(KSTARS_LITE) grabGesture(Qt::PinchGesture); grabGesture(Qt::TapAndHoldGesture); #endif m_Scale = 1.0; ZoomRect = QRect(); // set the default cursor setMouseCursorShape(static_cast(Options::defaultCursor())); QPalette p = palette(); p.setColor(QPalette::Window, QColor(data->colorScheme()->colorNamed("SkyColor"))); setPalette(p); setFocusPolicy(Qt::StrongFocus); setMinimumSize(380, 250); setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setStyleSheet("QGraphicsView { border-style: none; }"); setMouseTracking(true); //Generate MouseMove events! midMouseButtonDown = false; mouseButtonDown = false; slewing = false; clockSlewing = false; ClickedObject = nullptr; FocusObject = nullptr; m_SkyMapDraw = nullptr; pmenu = new KSPopupMenu(); setupProjector(); //Initialize Transient label stuff m_HoverTimer.setSingleShot(true); // using this timer as a single shot timer connect(&m_HoverTimer, SIGNAL(timeout()), this, SLOT(slotTransientLabel())); connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus())); connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool))); // Time infobox m_timeBox = new InfoBoxWidget(Options::shadeTimeBox(), Options::positionTimeBox(), Options::stickyTimeBox(), QStringList(), this); m_timeBox->setVisible(Options::showTimeBox()); connect(data->clock(), SIGNAL(timeChanged()), m_timeBox, SLOT(slotTimeChanged())); connect(data->clock(), SIGNAL(timeAdvanced()), m_timeBox, SLOT(slotTimeChanged())); // Geo infobox m_geoBox = new InfoBoxWidget(Options::shadeGeoBox(), Options::positionGeoBox(), Options::stickyGeoBox(), QStringList(), this); m_geoBox->setVisible(Options::showGeoBox()); connect(data, SIGNAL(geoChanged()), m_geoBox, SLOT(slotGeoChanged())); // Object infobox m_objBox = new InfoBoxWidget(Options::shadeFocusBox(), Options::positionFocusBox(), Options::stickyFocusBox(), QStringList(), this); m_objBox->setVisible(Options::showFocusBox()); connect(this, SIGNAL(objectChanged(SkyObject*)), m_objBox, SLOT(slotObjectChanged(SkyObject*))); connect(this, SIGNAL(positionChanged(SkyPoint*)), m_objBox, SLOT(slotPointChanged(SkyPoint*))); m_SkyMapDraw = new SkyMapQDraw(this); m_SkyMapDraw->setMouseTracking(true); m_SkyMapDraw->setParent(this->viewport()); m_SkyMapDraw->show(); m_iboxes = new InfoBoxes(m_SkyMapDraw); m_iboxes->setVisible(Options::showInfoBoxes()); m_iboxes->addInfoBox(m_timeBox); m_iboxes->addInfoBox(m_geoBox); m_iboxes->addInfoBox(m_objBox); } void SkyMap::slotToggleGeoBox(bool flag) { m_geoBox->setVisible(flag); } void SkyMap::slotToggleFocusBox(bool flag) { m_objBox->setVisible(flag); } void SkyMap::slotToggleTimeBox(bool flag) { m_timeBox->setVisible(flag); } void SkyMap::slotToggleInfoboxes(bool flag) { m_iboxes->setVisible(flag); Options::setShowInfoBoxes(flag); } SkyMap::~SkyMap() { /* == Save infoxes status into Options == */ //Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget())); // Time box Options::setPositionTimeBox(m_timeBox->pos()); Options::setShadeTimeBox(m_timeBox->shaded()); Options::setStickyTimeBox(m_timeBox->sticky()); Options::setShowTimeBox(m_timeBox->isVisibleTo(m_iboxes)); // Geo box Options::setPositionGeoBox(m_geoBox->pos()); Options::setShadeGeoBox(m_geoBox->shaded()); Options::setStickyGeoBox(m_geoBox->sticky()); Options::setShowGeoBox(m_geoBox->isVisibleTo(m_iboxes)); // Obj box Options::setPositionFocusBox(m_objBox->pos()); Options::setShadeFocusBox(m_objBox->shaded()); Options::setStickyFocusBox(m_objBox->sticky()); Options::setShowFocusBox(m_objBox->isVisibleTo(m_iboxes)); //store focus values in Options //If not tracking and using Alt/Az coords, stor the Alt/Az coordinates if (Options::useAltAz() && !Options::isTracking()) { Options::setFocusRA(focus()->az().Degrees()); Options::setFocusDec(focus()->alt().Degrees()); } else { Options::setFocusRA(focus()->ra().Hours()); Options::setFocusDec(focus()->dec().Degrees()); } #ifdef HAVE_OPENGL delete m_SkyMapGLDraw; delete m_SkyMapQDraw; m_SkyMapDraw = 0; // Just a formality #else delete m_SkyMapDraw; #endif delete pmenu; delete m_proj; pinstance = nullptr; } void SkyMap::showFocusCoords() { if (focusObject() && Options::isTracking()) emit objectChanged(focusObject()); else emit positionChanged(focus()); } void SkyMap::slotTransientLabel() { //This function is only called if the HoverTimer manages to timeout. //(HoverTimer is restarted with every mouseMoveEvent; so if it times //out, that means there was no mouse movement for HOVER_INTERVAL msec.) if (hasFocus() && !slewing && !(Options::useAltAz() && Options::showGround() && SkyPoint::refract(m_MousePoint.alt()).Degrees() < 0.0)) { double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *so = data->skyComposite()->objectNearest(&m_MousePoint, maxrad); if (so && !isObjectLabeled(so)) { QToolTip::showText(QCursor::pos(), i18n("%1: %2m", so->translatedLongName(), QString::number(so->mag(), 'f', 1)), this); } } } //Slots void SkyMap::setClickedObject(SkyObject *o) { ClickedObject = o; } void SkyMap::setFocusObject(SkyObject *o) { FocusObject = o; if (FocusObject) Options::setFocusObject(FocusObject->name()); else Options::setFocusObject(i18n("nothing")); } void SkyMap::slotCenter() { KStars *kstars = KStars::Instance(); TrailObject *trailObj = dynamic_cast(focusObject()); SkyPoint *foc; if(ClickedObject != nullptr) foc = ClickedObject; else foc = &ClickedPoint; if (Options::useAltAz()) { // JM 2016-09-12: Following call has problems when ra0/dec0 of an object are not valid for example // because they're solar system bodies. So it creates a lot of issues. It is disabled and centering // works correctly for all different body types as I tested. //DeepSkyObject *dso = dynamic_cast(focusObject()); //if (dso) // foc->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); // JM 2018-05-06: No need to do the above foc->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } else foc->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); qCDebug(KSTARS) << "Centering on " << foc->ra().toHMSString() << foc->dec().toDMSString(); //clear the planet trail of old focusObject, if it was temporary if (trailObj && data->temporaryTrail) { trailObj->clearTrail(); data->temporaryTrail = false; } //If the requested object is below the opaque horizon, issue a warning message //(unless user is already pointed below the horizon) if (Options::useAltAz() && Options::showGround() && focus()->alt().Degrees() > -1.0 && foc->alt().Degrees() < -1.0) { QString caption = i18n("Requested Position Below Horizon"); QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?"); if (KMessageBox::warningYesNo(this, message, caption, KGuiItem(i18n("Go Anyway")), KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz") == KMessageBox::No) { setClickedObject(nullptr); setFocusObject(nullptr); Options::setIsTracking(false); return; } } //set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset //destination to previous object... setFocusObject(ClickedObject); if(ClickedObject == nullptr) setFocusPoint(&ClickedPoint); Options::setIsTracking(true); if (kstars) { kstars->actionCollection() ->action("track_object") ->setIcon(QIcon::fromTheme("document-encrypt")); kstars->actionCollection()->action("track_object")->setText(i18n("Stop &Tracking")); } //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail if (Options::useAutoTrail() && trailObj && trailObj->hasTrail()) { trailObj->addToTrail(); data->temporaryTrail = true; } //update the destination to the selected coordinates if (Options::useAltAz()) { setDestinationAltAz(foc->altRefracted(), foc->az()); } else { setDestination(*foc); } foc->EquatorialToHorizontal(data->lst(), data->geo()->lat()); //display coordinates in statusBar emit mousePointChanged(foc); showFocusCoords(); //update FocusBox } void SkyMap::slotUpdateSky(bool now) { // Code moved from KStarsData::updateTime() //Update focus updateFocus(); if (now) QTimer::singleShot( 0, this, SLOT(forceUpdateNow())); // Why is it done this way rather than just calling forceUpdateNow()? -- asimha // --> Opening a neww thread? -- Valentin else forceUpdate(); } void SkyMap::slotDSS() { dms ra(0.0), dec(0.0); QString urlstring; //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. if (clickedObject()) { urlstring = KSDssDownloader::getDSSURL(clickedObject()); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); ra = deprecessedPoint.ra(); dec = deprecessedPoint.dec(); urlstring = KSDssDownloader::getDSSURL(ra, dec); // Use default size for non-objects } QUrl url(urlstring); KStars *kstars = KStars::Instance(); if (kstars) { new ImageViewer( url, i18n("Digitized Sky Survey image provided by the Space Telescope Science Institute [public domain]."), this); //iv->show(); } } void SkyMap::slotCopyCoordinates() { dms J2000RA(0.0), J2000DE(0.0), JNowRA(0.0), JNowDE(0.0), Az, Alt; if (clickedObject()) { J2000RA = clickedObject()->ra0(); J2000DE = clickedObject()->dec0(); JNowRA = clickedObject()->ra(); JNowDE = clickedObject()->dec(); Az = clickedObject()->az(); Alt = clickedObject()->alt(); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); deprecessedPoint.EquatorialToHorizontal(data->lst(), data->geo()->lat()); J2000RA = deprecessedPoint.ra0(); J2000DE = deprecessedPoint.dec0(); JNowRA = deprecessedPoint.ra(); JNowDE = deprecessedPoint.dec(); Az = deprecessedPoint.az(); Alt = deprecessedPoint.alt(); } QApplication::clipboard()->setText(i18nc("Equatorial & Horizontal Coordinates", "JNow:\t%1\t%2\nJ2000:\t%3\t%4\nAzAlt:\t%5\t%6", JNowRA.toHMSString(), JNowDE.toDMSString(), J2000RA.toHMSString(), J2000DE.toDMSString(), Az.toDMSString(), Alt.toDMSString())); } void SkyMap::slotSDSS() { // TODO: Remove code duplication -- we have the same stuff // implemented in ObservingList::setCurrentImage() etc. in // tools/observinglist.cpp; must try to de-duplicate as much as // possible. QString URLprefix("http://casjobs.sdss.org/ImgCutoutDR6/getjpeg.aspx?"); QString URLsuffix("&scale=1.0&width=600&height=600&opt=GST&query=SR(10,20)"); dms ra(0.0), dec(0.0); QString RAString, DecString; //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. if (clickedObject()) { ra = clickedObject()->ra0(); dec = clickedObject()->dec0(); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); ra = deprecessedPoint.ra(); dec = deprecessedPoint.dec(); } RAString = QString::asprintf("ra=%f", ra.Degrees()); DecString = QString::asprintf("&dec=%f", dec.Degrees()); //concat all the segments into the kview command line: QUrl url(URLprefix + RAString + DecString + URLsuffix); KStars *kstars = KStars::Instance(); if (kstars) { new ImageViewer(url, i18n("Sloan Digital Sky Survey image provided by the Astrophysical Research Consortium [free " "for non-commercial use]."), this); //iv->show(); } } void SkyMap::slotEyepieceView() { KStars::Instance()->slotEyepieceView((clickedObject() ? clickedObject() : clickedPoint())); } void SkyMap::slotBeginAngularDistance() { beginRulerMode(false); } void SkyMap::slotBeginStarHop() { beginRulerMode(true); } void SkyMap::beginRulerMode(bool starHopRuler) { rulerMode = true; starHopDefineMode = starHopRuler; AngularRuler.clear(); //If the cursor is near a SkyObject, reset the AngularRuler's //start point to the position of the SkyObject double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); if (so) { AngularRuler.append(so); AngularRuler.append(so); m_rulerStartPoint = so; } else { AngularRuler.append(clickedPoint()); AngularRuler.append(clickedPoint()); m_rulerStartPoint = clickedPoint(); } AngularRuler.update(data); } void SkyMap::slotEndRulerMode() { if (!rulerMode) return; if (!starHopDefineMode) // Angular Ruler { QString sbMessage; //If the cursor is near a SkyObject, reset the AngularRuler's //end point to the position of the SkyObject double maxrad = 1000.0 / Options::zoomFactor(); SkyPoint *rulerEndPoint; SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); if (so) { AngularRuler.setPoint(1, so); sbMessage = so->translatedLongName() + " "; rulerEndPoint = so; } else { AngularRuler.setPoint(1, clickedPoint()); rulerEndPoint = clickedPoint(); } rulerMode = false; AngularRuler.update(data); dms angularDistance = AngularRuler.angularSize(); sbMessage += i18n("Angular distance: %1", angularDistance.toDMSString()); const StarObject *p1 = dynamic_cast(m_rulerStartPoint); const StarObject *p2 = dynamic_cast(rulerEndPoint); qCDebug(KSTARS) << "Starobjects? " << p1 << p2; if (p1 && p2) qCDebug(KSTARS) << "Distances: " << p1->distance() << "pc; " << p2->distance() << "pc"; if (p1 && p2 && std::isfinite(p1->distance()) && std::isfinite(p2->distance()) && p1->distance() > 0 && p2->distance() > 0) { double dist = sqrt(p1->distance() * p1->distance() + p2->distance() * p2->distance() - 2 * p1->distance() * p2->distance() * cos(angularDistance.radians())); qCDebug(KSTARS) << "Could calculate physical distance: " << dist << " pc"; sbMessage += i18n("; Physical distance: %1 pc", QString::number(dist)); } AngularRuler.clear(); // Create unobsructive message box with suicidal tendencies // to display result. InfoBoxWidget *box = new InfoBoxWidget(true, mapFromGlobal(QCursor::pos()), 0, QStringList(sbMessage), this); connect(box, SIGNAL(clicked()), box, SLOT(deleteLater())); QTimer::singleShot(5000, box, SLOT(deleteLater())); box->adjust(); box->show(); } else // Star Hop { StarHopperDialog *shd = new StarHopperDialog(this); const SkyPoint &startHop = *AngularRuler.point(0); const SkyPoint &stopHop = *clickedPoint(); double fov; // Field of view in arcminutes bool ok; // true if user did not cancel the operation if (data->getAvailableFOVs().size() == 1) { // Exactly 1 FOV symbol visible, so use that. Also assume a circular FOV of size min{sizeX, sizeY} FOV *f = data->getAvailableFOVs().first(); fov = ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX()); ok = true; } else if (!data->getAvailableFOVs().isEmpty()) { // Ask the user to choose from a list of available FOVs. FOV const *f; QMap nameToFovMap; foreach (f, data->getAvailableFOVs()) { nameToFovMap.insert(f->name(), ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX())); } fov = nameToFovMap[QInputDialog::getItem(this, i18n("Star Hopper: Choose a field-of-view"), i18n("FOV to use for star hopping:"), nameToFovMap.uniqueKeys(), 0, false, &ok)]; } else { // Ask the user to enter a field of view fov = QInputDialog::getDouble(this, i18n("Star Hopper: Enter field-of-view to use"), i18n("FOV to use for star hopping (in arcminutes):"), 60.0, 1.0, 600.0, 1, &ok); } Q_ASSERT(fov > 0.0); if (ok) { qCDebug(KSTARS) << "fov = " << fov; shd->starHop(startHop, stopHop, fov / 60.0, 9.0); //FIXME: Hardcoded maglimit value shd->show(); } rulerMode = false; } } void SkyMap::slotCancelRulerMode(void) { rulerMode = false; AngularRuler.clear(); } void SkyMap::slotAddFlag() { KStars *ks = KStars::Instance(); // popup FlagManager window and update coordinates ks->slotFlagManager(); ks->flagManager()->clearFields(); //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. dms J2000RA, J2000DE; if (clickedObject()) { J2000RA = clickedObject()->ra0(); J2000DE = clickedObject()->dec0(); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); J2000RA = deprecessedPoint.ra(); J2000DE = deprecessedPoint.dec(); } ks->flagManager()->setRaDec(J2000RA, J2000DE); } void SkyMap::slotEditFlag(int flagIdx) { KStars *ks = KStars::Instance(); // popup FlagManager window and switch to selected flag ks->slotFlagManager(); ks->flagManager()->showFlag(flagIdx); } void SkyMap::slotDeleteFlag(int flagIdx) { KStars *ks = KStars::Instance(); ks->data()->skyComposite()->flags()->remove(flagIdx); ks->data()->skyComposite()->flags()->saveToFile(); // if there is FlagManager created, update its flag model if (ks->flagManager()) { ks->flagManager()->deleteFlagItem(flagIdx); } } void SkyMap::slotImage() { QString message = ((QAction *)sender())->text(); message = message.remove('&'); //Get rid of accelerator markers // Need to do this because we are comparing translated strings int index = -1; for (int i = 0; i < clickedObject()->ImageTitle().size(); ++i) { if (i18nc("Image/info menu item (should be translated)", clickedObject()->ImageTitle().at(i).toLocal8Bit().data()) == message) { index = i; break; } } QString sURL; if (index >= 0 && index < clickedObject()->ImageList().size()) { sURL = clickedObject()->ImageList()[index]; } else { qCWarning(KSTARS) << "ImageList index out of bounds: " << index; if (index == -1) { qCWarning(KSTARS) << "Message string \"" << message << "\" not found in ImageTitle."; qCDebug(KSTARS) << clickedObject()->ImageTitle(); } } QUrl url(sURL); if (!url.isEmpty()) new ImageViewer(url, clickedObject()->messageFromTitle(message), this); } void SkyMap::slotInfo() { QString message = ((QAction *)sender())->text(); message = message.remove('&'); //Get rid of accelerator markers // Need to do this because we are comparing translated strings int index = -1; for (int i = 0; i < clickedObject()->InfoTitle().size(); ++i) { if (i18nc("Image/info menu item (should be translated)", clickedObject()->InfoTitle().at(i).toLocal8Bit().data()) == message) { index = i; break; } } QString sURL; if (index >= 0 && index < clickedObject()->InfoList().size()) { sURL = clickedObject()->InfoList()[index]; } else { qCWarning(KSTARS) << "InfoList index out of bounds: " << index; if (index == -1) { qCWarning(KSTARS) << "Message string \"" << message << "\" not found in InfoTitle."; qCDebug(KSTARS) << clickedObject()->InfoTitle(); } } QUrl url(sURL); if (!url.isEmpty()) QDesktopServices::openUrl(url); } bool SkyMap::isObjectLabeled(SkyObject *object) { return data->skyComposite()->labelObjects().contains(object); } SkyPoint SkyMap::getCenterPoint() { SkyPoint retVal; // FIXME: subtraction of these 0.00001 is a simple workaround, because wrong // SkyPoint is returned when _exact_ center of SkyMap is passed to the projector. retVal = projector()->fromScreen(QPointF((qreal)width() / 2 - 0.00001, (qreal)height() / 2 - 0.00001), data->lst(), data->geo()->lat()); return retVal; } void SkyMap::slotRemoveObjectLabel() { data->skyComposite()->removeNameLabel(clickedObject()); forceUpdate(); } void SkyMap::slotRemoveCustomObject() { SkyObject* object = clickedObject(); // The object must be removed from the catalog... data->skyComposite()->internetResolvedComponent()->removeObject(*object); // ...and then in the rest of the places. emit removeSkyObject(object); data->skyComposite()->removeFromNames(object); data->skyComposite()->removeFromLists(object); } void SkyMap::slotAddObjectLabel() { data->skyComposite()->addNameLabel(clickedObject()); forceUpdate(); } void SkyMap::slotRemovePlanetTrail() { TrailObject *tobj = dynamic_cast(clickedObject()); if (tobj) { tobj->clearTrail(); forceUpdate(); } } void SkyMap::slotAddPlanetTrail() { TrailObject *tobj = dynamic_cast(clickedObject()); if (tobj) { tobj->addToTrail(); forceUpdate(); } } void SkyMap::slotDetail() { // check if object is selected if (!clickedObject()) { KMessageBox::sorry(this, i18n("No object selected."), i18n("Object Details")); return; } DetailDialog *detail = new DetailDialog(clickedObject(), data->ut(), data->geo(), KStars::Instance()); detail->setAttribute(Qt::WA_DeleteOnClose); detail->show(); } void SkyMap::slotObjectSelected() { if (m_objPointingMode && KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->pointingDone(clickedObject()); m_objPointingMode = false; } } void SkyMap::slotCancelLegendPreviewMode() { m_previewLegend = false; forceUpdate(true); KStars::Instance()->showImgExportDialog(); } void SkyMap::slotFinishFovCaptureMode() { if (m_fovCaptureMode && KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->fovCaptureDone(); m_fovCaptureMode = false; } } void SkyMap::slotCaptureFov() { if (KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->captureFov(); } } void SkyMap::slotClockSlewing() { //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock. if ((fabs(data->clock()->scale()) > Options::slewTimeScale()) ^ clockSlewing) { data->clock()->setManualMode(!clockSlewing); clockSlewing = !clockSlewing; // don't change automatically the DST status KStars *kstars = KStars::Instance(); if (kstars) kstars->updateTime(false); } } void SkyMap::setFocus(SkyPoint *p) { setFocus(p->ra(), p->dec()); } void SkyMap::setFocus(const dms &ra, const dms &dec) { Options::setFocusRA(ra.Hours()); Options::setFocusDec(dec.Degrees()); focus()->set(ra, dec); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } void SkyMap::setFocusAltAz(const dms &alt, const dms &az) { Options::setFocusRA(focus()->ra().Hours()); Options::setFocusDec(focus()->dec().Degrees()); focus()->setAlt(alt); focus()->setAz(az); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); slewing = false; forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work. } void SkyMap::setDestination(const SkyPoint &p) { setDestination(p.ra(), p.dec()); } void SkyMap::setDestination(const dms &ra, const dms &dec) { destination()->set(ra, dec); destination()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); emit destinationChanged(); } void SkyMap::setDestinationAltAz(const dms &alt, const dms &az) { destination()->setAlt(alt); destination()->setAz(az); destination()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); emit destinationChanged(); } -void SkyMap::setClickedPoint(SkyPoint *f) +void SkyMap::setClickedPoint(const SkyPoint *f) { ClickedPoint = *f; } void SkyMap::updateFocus() { if (slewing) return; //Tracking on an object if (Options::isTracking() && focusObject() != nullptr) { if (Options::useAltAz()) { //Tracking any object in Alt/Az mode requires focus updates focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setFocusAltAz(focusObject()->altRefracted(), focusObject()->az()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); setDestination(*focus()); } else { //Tracking in equatorial coords setFocus(focusObject()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setDestination(*focus()); } //Tracking on empty sky } else if (Options::isTracking() && focusPoint() != nullptr) { if (Options::useAltAz()) { //Tracking on empty sky in Alt/Az mode setFocus(focusPoint()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setDestination(*focus()); } // Not tracking and not slewing, let sky drift by // This means that horizontal coordinates are constant. } else { focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } } void SkyMap::slewFocus() { //Don't slew if the mouse button is pressed //Also, no animated slews if the Manual Clock is active //08/2002: added possibility for one-time skipping of slew with snapNextFocus if (!mouseButtonDown) { bool goSlew = (Options::useAnimatedSlewing() && !data->snapNextFocus()) && !(data->clock()->isManualMode() && data->clock()->isActive()); if (goSlew) { double dX, dY; double maxstep = 10.0; if (Options::useAltAz()) { dX = destination()->az().Degrees() - focus()->az().Degrees(); dY = destination()->alt().Degrees() - focus()->alt().Degrees(); } else { dX = destination()->ra().Degrees() - focus()->ra().Degrees(); dY = destination()->dec().Degrees() - focus()->dec().Degrees(); } //switch directions to go the short way around the celestial sphere, if necessary. dX = KSUtils::reduceAngle(dX, -180.0, 180.0); double r0 = sqrt(dX * dX + dY * dY); if (r0 < 20.0) //smaller slews have smaller maxstep { maxstep *= (10.0 + 0.5 * r0) / 20.0; } double step = 0.5; double r = r0; while (r > step) { //DEBUG //qDebug() << step << ": " << r << ": " << r0; double fX = dX / r; double fY = dY / r; if (Options::useAltAz()) { focus()->setAlt(focus()->alt().Degrees() + fY * step); focus()->setAz(dms(focus()->az().Degrees() + fX * step).reduce()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { fX = fX / 15.; //convert RA degrees to hours SkyPoint newFocus(focus()->ra().Hours() + fX * step, focus()->dec().Degrees() + fY * step); setFocus(&newFocus); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } slewing = true; forceUpdate(); qApp->processEvents(); //keep up with other stuff if (Options::useAltAz()) { dX = destination()->az().Degrees() - focus()->az().Degrees(); dY = destination()->alt().Degrees() - focus()->alt().Degrees(); } else { dX = destination()->ra().Degrees() - focus()->ra().Degrees(); dY = destination()->dec().Degrees() - focus()->dec().Degrees(); } //switch directions to go the short way around the celestial sphere, if necessary. dX = KSUtils::reduceAngle(dX, -180.0, 180.0); r = sqrt(dX * dX + dY * dY); //Modify step according to a cosine-shaped profile //centered on the midpoint of the slew //NOTE: don't allow the full range from -PI/2 to PI/2 //because the slew will never reach the destination as //the speed approaches zero at the end! double t = dms::PI * (r - 0.5 * r0) / (1.05 * r0); step = cos(t) * maxstep; } } //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination //set focus=destination. if (Options::useAltAz()) { setFocusAltAz(destination()->alt(), destination()->az()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { setFocus(destination()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } slewing = false; //Turn off snapNextFocus, we only want it to happen once if (data->snapNextFocus()) { data->setSnapNextFocus(false); } //Start the HoverTimer. if the user leaves the mouse in place after a slew, //we want to attach a label to the nearest object. if (Options::useHoverLabel()) m_HoverTimer.start(HOVER_INTERVAL); forceUpdate(); } } void SkyMap::slotZoomIn() { setZoomFactor(Options::zoomFactor() * DZOOM); } void SkyMap::slotZoomOut() { setZoomFactor(Options::zoomFactor() / DZOOM); } void SkyMap::slotZoomDefault() { setZoomFactor(DEFAULTZOOM); } void SkyMap::setZoomFactor(double factor) { Options::setZoomFactor(KSUtils::clamp(factor, MINZOOM, MAXZOOM)); forceUpdate(); emit zoomChanged(); } // force a new calculation of the skymap (used instead of update(), which may skip the redraw) // if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue // also, determine new coordinates of mouse cursor. void SkyMap::forceUpdate(bool now) { QPoint mp(mapFromGlobal(QCursor::pos())); if (!projector()->unusablePoint(mp)) { //determine RA, Dec of mouse pointer m_MousePoint = projector()->fromScreen(mp, data->lst(), data->geo()->lat()); } computeSkymap = true; // Ensure that stars are recomputed data->incUpdateID(); if (now) m_SkyMapDraw->repaint(); else m_SkyMapDraw->update(); } float SkyMap::fov() { float diagonalPixels = sqrt(static_cast(width() * width() + height() * height())); return diagonalPixels / (2 * Options::zoomFactor() * dms::DegToRad); } void SkyMap::setupProjector() { //Update View Parameters for projection ViewParams p; p.focus = focus(); p.height = height(); p.width = width(); p.useAltAz = Options::useAltAz(); p.useRefraction = Options::useRefraction(); p.zoomFactor = Options::zoomFactor(); p.fillGround = Options::showGround(); //Check if we need a new projector if (m_proj && Options::projection() == m_proj->type()) m_proj->setViewParams(p); else { delete m_proj; switch (Options::projection()) { case Gnomonic: m_proj = new GnomonicProjector(p); break; case Stereographic: m_proj = new StereographicProjector(p); break; case Orthographic: m_proj = new OrthographicProjector(p); break; case AzimuthalEquidistant: m_proj = new AzimuthalEquidistantProjector(p); break; case Equirectangular: m_proj = new EquirectangularProjector(p); break; case Lambert: default: //TODO: implement other projection classes m_proj = new LambertProjector(p); break; } } } void SkyMap::setZoomMouseCursor() { mouseMoveCursor = false; // no mousemove cursor QBitmap cursor = zoomCursorBitmap(2); QBitmap mask = zoomCursorBitmap(4); setCursor(QCursor(cursor, mask)); } void SkyMap::setMouseCursorShape(Cursor type) { // no mousemove cursor mouseMoveCursor = false; switch (type) { case Cross: { QBitmap cursor = defaultCursorBitmap(2); QBitmap mask = defaultCursorBitmap(3); setCursor(QCursor(cursor, mask)); } break; case Circle: { QBitmap cursor = circleCursorBitmap(2); QBitmap mask = circleCursorBitmap(3); setCursor(QCursor(cursor, mask)); } break; case NoCursor: setCursor(Qt::ArrowCursor); break; } } void SkyMap::setMouseMoveCursor() { if (mouseButtonDown) { setCursor(Qt::SizeAllCursor); // cursor shape defined in qt mouseMoveCursor = true; } } void SkyMap::updateAngleRuler() { if (rulerMode && (!pmenu || !pmenu->isVisible())) AngularRuler.setPoint(1, &m_MousePoint); AngularRuler.update(data); } bool SkyMap::isSlewing() const { return (slewing || (clockSlewing && data->clock()->isActive())); } void SkyMap::slotStartXplanetViewer() { if(clickedObject()) new XPlanetImageViewer(clickedObject()->name(), this); else new XPlanetImageViewer(i18n("Saturn"), this); } diff --git a/kstars/skymap.h b/kstars/skymap.h index b5fe5ced8..9a890b878 100644 --- a/kstars/skymap.h +++ b/kstars/skymap.h @@ -1,753 +1,753 @@ /*************************************************************************** skymap.h - K Desktop Planetarium ------------------- begin : Sat Feb 10 2001 copyright : (C) 2001 by Jason Harris email : jharris@30doradus.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #pragma once #include "config-kstars.h" #include "skymapdrawabstract.h" #include "printing/legend.h" #include "skyobjects/skypoint.h" #include "skyobjects/skyline.h" #include #include #include class QPainter; class QPaintDevice; class dms; class InfoBoxes; class InfoBoxWidget; class KSPopupMenu; class KStarsData; class Projector; class SkyObject; #ifdef HAVE_OPENGL class SkyMapGLDraw; class SkyMapQDraw; #endif /** * @class SkyMap * * This is the canvas on which the sky is painted. It's the main widget for KStars. * Contains SkyPoint members for the map's Focus (current central position), Destination * (requested central position), FocusPoint (next queued position to be focused), * MousePoint (position of mouse cursor), and ClickedPoint (position of last mouse click). * Also contains the InfoBoxes for on-screen data display. * * SkyMap handles most user interaction events (both mouse and keyboard). * * @short Canvas widget for displaying the sky bitmap; also handles user interaction events. * * @author Jason Harris * @version 1.0 */ class SkyMap : public QGraphicsView { Q_OBJECT friend class SkyMapDrawAbstract; // FIXME: SkyMapDrawAbstract requires a lot of access to SkyMap friend class SkyMapQDraw; // FIXME: SkyMapQDraw requires access to computeSkymap protected: /** *Constructor. Read stored settings from KConfig object (focus position, *zoom factor, sky color, etc.). Run initPopupMenus(). */ SkyMap(); public: static SkyMap *Create(); static SkyMap *Instance(); static bool IsSlewing() { return pinstance->isSlewing(); } /** Destructor (empty) */ ~SkyMap() override; enum Projection { Lambert, AzimuthalEquidistant, Orthographic, Equirectangular, Stereographic, Gnomonic, UnknownProjection }; enum Cursor { NoCursor, Cross, Circle, }; /** @return the angular field of view of the sky map, in degrees. *@note it must use either the height or the width of the window to calculate the *FOV angle. It chooses whichever is larger. */ float fov(); /** @short Update object name and coordinates in the Focus InfoBox */ void showFocusCoords(); /** @short Update the focus position according to current options. */ void updateFocus(); /** @short Retrieve the Focus point; the position on the sky at the *center of the skymap. *@return a pointer to the central focus point of the sky map */ SkyPoint *focus() { return &Focus; } /** @short retrieve the Destination position. * *The Destination is the point on the sky to which the focus will *be moved. * *@return a pointer to the destination point of the sky map */ SkyPoint *destination() { return &Destination; } /** @short retrieve the FocusPoint position. * *The FocusPoint stores the position on the sky that is to be *focused next. This is not exactly the same as the Destination *point, because when the Destination is set, it will begin slewing *immediately. * *@return a pointer to the sky point which is to be focused next. */ SkyPoint *focusPoint() { return &FocusPoint; } /** @short sets the central focus point of the sky map. *@param f a pointer to the SkyPoint the map should be centered on */ void setFocus(SkyPoint *f); /** @short sets the focus point of the skymap, using ra/dec coordinates * *@note This function behaves essentially like the above function. *It differs only in the data types of its arguments. * *@param ra the new right ascension *@param dec the new declination */ void setFocus(const dms &ra, const dms &dec); /** @short sets the focus point of the sky map, using its alt/az coordinates *@param alt the new altitude *@param az the new azimuth */ void setFocusAltAz(const dms &alt, const dms &az); /** @short sets the destination point of the sky map. *@note setDestination() emits the destinationChanged() SIGNAL, *which triggers the SLOT function SkyMap::slewFocus(). This *function iteratively steps the Focus point toward Destination, *repainting the sky at each step (if Options::useAnimatedSlewing()==true). *@param f a pointer to the SkyPoint the map should slew to */ void setDestination(const SkyPoint &f); /** @short sets the destination point of the skymap, using ra/dec coordinates. * *@note This function behaves essentially like the above function. *It differs only in the data types of its arguments. * *@param ra the new right ascension *@param dec the new declination */ void setDestination(const dms &ra, const dms &dec); /** @short sets the destination point of the sky map, using its alt/az coordinates. *@param alt the new altitude *@param az the new azimuth */ void setDestinationAltAz(const dms &alt, const dms &az); /** @short set the FocusPoint; the position that is to be the next Destination. *@param f a pointer to the FocusPoint SkyPoint. */ void setFocusPoint(SkyPoint *f) { if (f) FocusPoint = *f; } /** @short Retrieve the ClickedPoint position. * *When the user clicks on a point in the sky map, the sky coordinates of the mouse *cursor are stored in the private member ClickedPoint. This function retrieves *a pointer to ClickedPoint. *@return a pointer to ClickedPoint, the sky coordinates where the user clicked. */ SkyPoint *clickedPoint() { return &ClickedPoint; } /** * @short Retrieve the mouse pointer position. * * @return The sky coordinates where the mouse pointer is over. */ SkyPoint *mousePoint() { return &m_MousePoint; } /** @short Set the ClickedPoint to the skypoint given as an argument. *@param f pointer to the new ClickedPoint. */ - void setClickedPoint(SkyPoint *f); + void setClickedPoint(const SkyPoint *f); /** @short Retrieve the object nearest to a mouse click event. * *If the user clicks on the sky map, a pointer to the nearest SkyObject is stored in *the private member ClickedObject. This function returns the ClickedObject pointer, *or nullptr if there is no CLickedObject. *@return a pointer to the object nearest to a user mouse click. */ SkyObject *clickedObject() const { return ClickedObject; } /** @short Set the ClickedObject pointer to the argument. *@param o pointer to the SkyObject to be assigned as the ClickedObject */ void setClickedObject(SkyObject *o); /** @short Retrieve the object which is centered in the sky map. * *If the user centers the sky map on an object (by double-clicking or using the *Find Object dialog), a pointer to the "focused" object is stored in *the private member FocusObject. This function returns a pointer to the *FocusObject, or nullptr if there is not FocusObject. *@return a pointer to the object at the center of the sky map. */ SkyObject *focusObject() const { return FocusObject; } /** @short Set the FocusObject pointer to the argument. *@param o pointer to the SkyObject to be assigned as the FocusObject */ void setFocusObject(SkyObject *o); /** @short Call to set up the projector before a draw cycle. */ void setupProjector(); /** @ Set zoom factor. *@param factor zoom factor */ void setZoomFactor(double factor); bool isSlewing() const; // NOTE: This method is draw-backend independent. /** @short update the geometry of the angle ruler. */ void updateAngleRuler(); /** @return true if the object currently has a user label attached. *@note this function only checks for a label explicitly added to the object *with the right-click popup menu; other kinds of labels are not detected by *this function. *@param o pointer to the sky object to be tested for a User label. */ bool isObjectLabeled(SkyObject *o); /*@*@short Convenience function for shutting off tracking mode. Just calls KStars::slotTrack(). */ void stopTracking(); /** Get the current projector. @return a pointer to the current projector. */ inline const Projector *projector() const { return m_proj; } // NOTE: These dynamic casts must not segfault. If they do, it's good because we know that there is a problem. /** *@short Proxy method for SkyMapDrawAbstract::exportSkyImage() */ inline void exportSkyImage(QPaintDevice *pd, bool scale = false) { dynamic_cast(m_SkyMapDraw)->exportSkyImage(pd, scale); } inline void exportSkyImage(SkyQPainter *painter, bool scale = false) { dynamic_cast(m_SkyMapDraw)->exportSkyImage(painter, scale); } SkyMapDrawAbstract *getSkyMapDrawAbstract() { return dynamic_cast(m_SkyMapDraw); } /** *@short Proxy method for SkyMapDrawAbstract::drawObjectLabels() */ inline void drawObjectLabels(QList &labelObjects) { dynamic_cast(m_SkyMapDraw)->drawObjectLabels(labelObjects); } void setPreviewLegend(bool preview) { m_previewLegend = preview; } void setLegend(const Legend &legend) { m_legend = legend; } bool isInObjectPointingMode() const { return m_objPointingMode; } void setObjectPointingMode(bool enabled) { m_objPointingMode = enabled; } void setFovCaptureMode(bool enabled) { m_fovCaptureMode = enabled; } bool isInFovCaptureMode() const { return m_fovCaptureMode; } /** @short Sets the shape of the default mouse cursor. */ void setMouseCursorShape(Cursor type); SkyPoint getCenterPoint(); public slots: /** Recalculates the positions of objects in the sky, and then repaints the sky map. * If the positions don't need to be recalculated, use update() instead of forceUpdate(). * This saves a lot of CPU time. * @param now if true, paintEvent() is run immediately. Otherwise, it is added to the event queue */ void forceUpdate(bool now = false); /** @short Convenience function; simply calls forceUpdate(true). * @see forceUpdate() */ void forceUpdateNow() { forceUpdate(true); } /** * @short Update the focus point and call forceUpdate() * @param now is passed on to forceUpdate() */ void slotUpdateSky(bool now); /** Toggle visibility of geo infobox */ void slotToggleGeoBox(bool); /** Toggle visibility of focus infobox */ void slotToggleFocusBox(bool); /** Toggle visibility of time infobox */ void slotToggleTimeBox(bool); /** Toggle visibility of all infoboxes */ void slotToggleInfoboxes(bool); /** Step the Focus point toward the Destination point. Do this iteratively, redrawing the Sky * Map after each step, until the Focus point is within 1 step of the Destination point. * For the final step, snap directly to Destination, and redraw the map. */ void slewFocus(); /** @short Center the display at the point ClickedPoint. * * The essential part of the function is to simply set the Destination point, which will emit * the destinationChanged() SIGNAL, which triggers the slewFocus() SLOT. Additionally, this * function performs some bookkeeping tasks, such updating whether we are tracking the new * object/position, adding a Planet Trail if required, etc. * * @see destinationChanged() * @see slewFocus() */ void slotCenter(); /** @short Popup menu function: Display 1st-Generation DSS image with the Image Viewer. * @note the URL is generated using the coordinates of ClickedPoint. */ void slotDSS(); /** @short Popup menu function: Display Sloan Digital Sky Survey image with the Image Viewer. * @note the URL is generated using the coordinates of ClickedPoint. */ void slotSDSS(); /** * @brief slotCopyCoordinates Copies J2000 and JNow equatorial coordinates to the clipboard in addition to horizontal coords. */ void slotCopyCoordinates(); /** @short Popup menu function: Show webpage about ClickedObject * (only available for some objects). */ void slotInfo(); /** @short Popup menu function: Show image of ClickedObject * (only available for some objects). */ void slotImage(); /** @short Popup menu function: Show the Detailed Information window for ClickedObject. */ void slotDetail(); /** Add ClickedObject to KStarsData::ObjLabelList, which stores pointers to SkyObjects which * have User Labels attached. */ void slotAddObjectLabel(); /** Remove ClickedObject from KStarsData::ObjLabelList, which stores pointers to SkyObjects which * have User Labels attached. */ void slotRemoveObjectLabel(); /** Remove custom object from internet search in the local catalog */ void slotRemoveCustomObject(); /** @short Add a Planet Trail to ClickedObject. * @note Trails are added simply by calling KSPlanetBase::addToTrail() to add the first point. * as long as the trail is not empty, new points will be automatically appended to it. * @note if ClickedObject is not a Solar System body, this function does nothing. * @see KSPlanetBase::addToTrail() */ void slotAddPlanetTrail(); /** @short Remove the PlanetTrail from ClickedObject. * @note The Trail is removed by simply calling KSPlanetBase::clearTrail(). As long as * the trail is empty, no new points will be automatically appended. * @see KSPlanetBase::clearTrail() */ void slotRemovePlanetTrail(); /** Checks whether the timestep exceeds a threshold value. If so, sets * ClockSlewing=true and sets the SimClock to ManualMode. */ void slotClockSlewing(); /** Enables the angular distance measuring mode. It saves the first * position of the ruler in a SkyPoint. It makes difference between * having clicked on the skymap and not having done so * \note This method is draw-backend independent. */ void slotBeginAngularDistance(); void slotBeginStarHop(); // TODO: Add docs /** Computes the angular distance, prints the result in the status * bar and disables the angular distance measuring mode * If the user has clicked on the map the status bar shows the * name of the clicked object plus the angular distance. If * the user did not clicked on the map, just pressed ], only * the angular distance is printed * \note This method is draw-backend independent. */ void slotEndRulerMode(); /** Disables the angular distance measuring mode. Nothing is printed * in the status bar */ void slotCancelRulerMode(); /** @short Open Flag Manager window with clickedObject() RA and Dec entered. */ void slotAddFlag(); /** @short Open Flag Manager window with selected flag focused and ready to edit. *@param flagIdx index of flag to be edited. */ void slotEditFlag(int flagIdx); /** @short Delete selected flag. *@param flagIdx index of flag to be deleted. */ void slotDeleteFlag(int flagIdx); #ifdef HAVE_OPENGL void slotToggleGL(); #endif /** Run Xplanet Viewer to display images of the planets*/ void slotStartXplanetViewer(); /** Render eyepiece view */ void slotEyepieceView(); /** Zoom in one step. */ void slotZoomIn(); /** Zoom out one step. */ void slotZoomOut(); /** Set default zoom. */ void slotZoomDefault(); /** Object pointing for Printing Wizard done */ void slotObjectSelected(); void slotCancelLegendPreviewMode(); void slotFinishFovCaptureMode(); void slotCaptureFov(); signals: /** Emitted by setDestination(), and connected to slewFocus(). Whenever the Destination * point is changed, slewFocus() will iteratively step the Focus toward Destination * until it is reached. * @see SkyMap::setDestination() * @see SkyMap::slewFocus() */ void destinationChanged(); /** Emitted when zoom level is changed. */ void zoomChanged(); /** Emitted when current object changed. */ void objectChanged(SkyObject *); /** Emitted when pointing changed. (At least should) */ void positionChanged(SkyPoint *); /** Emitted when position under mouse changed. */ void mousePointChanged(SkyPoint *); /** Emitted when a position is clicked */ void positionClicked(SkyPoint *); /** Emitted when a position is clicked */ void objectClicked(SkyObject *); /** Emitted when a sky object is removed from the database */ void removeSkyObject(SkyObject *object); protected: bool event(QEvent *event) override; /** Process keystrokes: * @li arrow keys Slew the map * @li +/- keys Zoom in and out * @li Space Toggle between Horizontal and Equatorial coordinate systems * @li 0-9 Go to a major Solar System body (0=Sun; 1-9 are the major planets, except 3=Moon) * @li [ Place starting point for measuring an angular distance * @li ] End point for Angular Distance; display measurement. * @li Escape Cancel Angular measurement * @li ,/< Step backward one time step * @li ./> Step forward one time step */ void keyPressEvent(QKeyEvent *e) override; /** When keyRelease is triggered, just set the "slewing" flag to false, * and update the display (to draw objects that are hidden when slewing==true). */ void keyReleaseEvent(QKeyEvent *e) override; /** Determine RA, Dec coordinates of clicked location. Find the SkyObject * which is nearest to the clicked location. * * If left-clicked: Set set mouseButtonDown==true, slewing==true; display * nearest object name in status bar. * If right-clicked: display popup menu appropriate for nearest object. */ void mousePressEvent(QMouseEvent *e) override; /** set mouseButtonDown==false, slewing==false */ void mouseReleaseEvent(QMouseEvent *e) override; /** Center SkyMap at double-clicked location */ void mouseDoubleClickEvent(QMouseEvent *e) override; /** This function does several different things depending on the state of the program: * @li If Angle-measurement mode is active, update the end-ruler point to the mouse cursor, * and continue this function. * @li If we are defining a ZoomBox, update the ZoomBox rectangle, redraw the screen, * and return. * @li If dragging the mouse in the map, update focus such that RA, Dec under the mouse * cursor remains constant. * @li If just moving the mouse, simply update the curso coordinates in the status bar. */ void mouseMoveEvent(QMouseEvent *e) override; /** Zoom in and out with the mouse wheel. */ void wheelEvent(QWheelEvent *e) override; /** If the skymap will be resized, the sky must be new computed. So this * function calls explicitly new computing of the skymap. */ void resizeEvent(QResizeEvent *) override; private slots: /** @short display tooltip for object under cursor. It's called by m_HoverTimer. * if mouse didn't moved for last HOVER_INTERVAL milliseconds. */ void slotTransientLabel(); /** Set the shape of mouse cursor to a cross with 4 arrows. */ void setMouseMoveCursor(); private: /** @short Sets the shape of the mouse cursor to a magnifying glass. */ void setZoomMouseCursor(); /** Calculate the zoom factor for the given keyboard modifier */ double zoomFactor(const int modifier); /** calculate the magnitude factor (1, .5, .2, or .1) for the given * keyboard modifier. */ double magFactor(const int modifier); /** Decrease the magnitude limit by a step size determined by the * keyboard modifier. * @param modifier */ void decMagLimit(const int modifier); /** Increase the magnitude limit by a step size determined by the * keyboard modifier. * @param modifier */ void incMagLimit(const int modifier); /** Convenience routine to either zoom in or increase mag limit * depending on the Alt modifier. The Shift and Control modifiers * will adjust the size of the zoom or the mag step. * @param modifier */ void zoomInOrMagStep(const int modifier); /** Convenience routine to either zoom out or decrease mag limit * depending on the Alt modifier. The Shift and Control modifiers * will adjust the size of the zoom or the mag step. * @param modifier */ void zoomOutOrMagStep(const int modifier); void beginRulerMode(bool starHopRuler); // TODO: Add docs /** * @short Strart xplanet. * @param outputFile Output file path. */ void startXplanet(const QString &outputFile = ""); bool mouseButtonDown { false }; bool midMouseButtonDown { false }; /// True if mouseMoveEvent; needed by setMouseMoveCursor bool mouseMoveCursor { false }; bool slewing { false }; bool clockSlewing { false }; //if false only old pixmap will repainted with bitBlt(), this // saves a lot of cpu usage bool computeSkymap { false }; // True if we are either looking for angular distance or star hopping directions bool rulerMode { false }; // True only if we are looking for star hopping directions. If // false while rulerMode is true, it means we are measuring angular // distance. FIXME: Find a better way to do this bool starHopDefineMode { false }; double y0; double m_Scale; KStarsData *data { nullptr }; KSPopupMenu *pmenu { nullptr }; /// Coordinates of point under cursor. It's update in function mouseMoveEvent SkyPoint m_MousePoint; SkyPoint Focus, ClickedPoint, FocusPoint, Destination; SkyObject *ClickedObject { nullptr }; SkyObject *FocusObject { nullptr }; Projector *m_proj { nullptr }; SkyLine AngularRuler; //The line for measuring angles in the map QRect ZoomRect; //The manual-focus circle. // Mouse should not move for that interval to display tooltip static const int HOVER_INTERVAL = 500; // Timer for tooltips QTimer m_HoverTimer; // InfoBoxes. Used in destructor to save state InfoBoxWidget *m_timeBox { nullptr }; InfoBoxWidget *m_geoBox { nullptr }; InfoBoxWidget *m_objBox { nullptr }; InfoBoxes *m_iboxes { nullptr }; // legend bool m_previewLegend { false }; Legend m_legend; bool m_objPointingMode { false }; bool m_fovCaptureMode { false }; bool m_touchMode { false }; bool m_pinchMode { false }; bool m_tapAndHoldMode { false }; qreal m_pinchScale { 0.0 }; QWidget *m_SkyMapDraw { nullptr }; // Can be dynamic_cast<> to SkyMapDrawAbstract // NOTE: These are pointers to the individual widgets #ifdef HAVE_OPENGL SkyMapQDraw *m_SkyMapQDraw { nullptr }; SkyMapGLDraw *m_SkyMapGLDraw { nullptr }; #endif static SkyMap *pinstance; /// Good to keep the original ruler start-point for purposes of dynamic_cast const SkyPoint *m_rulerStartPoint { nullptr }; };