File indexing completed on 2024-06-16 09:53:43

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