File indexing completed on 2024-04-21 07:31:25

0001 /*
0002     SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 #include "skymaplite.h"
0006 #include "kstarsdata.h"
0007 #include "kstarslite.h"
0008 
0009 #include <QtMath>
0010 
0011 #include "kstarslite/skypointlite.h"
0012 #include "kstarslite/skyobjectlite.h"
0013 
0014 #include "kstarslite/skyitems/planetsitem.h"
0015 #include "Options.h"
0016 #include "projections/projector.h"
0017 #include "skymapcomposite.h"
0018 #include "ksutils.h"
0019 //#include <QTapSensor>
0020 
0021 void SkyMapLite::mousePressEvent(QMouseEvent *e)
0022 {
0023     if ((e->modifiers() & Qt::ControlModifier) && (e->button() == Qt::LeftButton))
0024     {
0025         ZoomRect.moveCenter(e->pos());
0026         setZoomMouseCursor();
0027         update(); //refresh without redrawing skymap
0028         return;
0029     }
0030     // if button is down and cursor is not moved set the move cursor after 500 ms
0031     QTimer::singleShot(500, this, SLOT(setMouseMoveCursor()));
0032 
0033     // break if point is unusable
0034     if (projector()->unusablePoint(e->pos()))
0035         return;
0036 
0037     if (!midMouseButtonDown && e->button() == Qt::MidButton)
0038     {
0039         y0                 = 0.5 * height() - e->y(); //record y pixel coordinate for middle-button zooming
0040         midMouseButtonDown = true;
0041     }
0042     if (!mouseButtonDown)
0043     {
0044         if (e->button() == Qt::LeftButton)
0045         {
0046             mouseButtonDown = true;
0047         }
0048 
0049         //determine RA, Dec of mouse pointer
0050         m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat());
0051         setClickedPoint(&m_MousePoint);
0052 
0053         //Find object nearest to clickedPoint()
0054         double maxrad  = 1000.0 / Options::zoomFactor();
0055         SkyObject *obj = data->skyComposite()->objectNearest(clickedPoint(), maxrad);
0056         setClickedObject(obj);
0057         if (obj)
0058             setClickedPoint(obj);
0059 
0060         switch (e->button())
0061         {
0062             case Qt::LeftButton:
0063             {
0064                 /*QString name;
0065                 if( clickedObject() )
0066                     //name = clickedObject()->translatedLongName();
0067                 else
0068                     //name = i18n( "Empty sky" );
0069                 //kstars->statusBar()->changeItem(name, 0 );
0070                 //kstars->statusBar()->showMessage(name, 0 );
0071 
0072                 emit positionClicked(&m_MousePoint);*/
0073             }
0074 
0075             break;
0076             case Qt::RightButton:
0077                 /*if( rulerMode ) {
0078                     // Compute angular distance.
0079                     slotEndRulerMode();
0080                 } else {*/
0081                 // Show popup menu
0082                 if (clickedObject())
0083                 {
0084                     emit objectLiteChanged();
0085                 }
0086                 else
0087                 {
0088                     emit pointLiteChanged();
0089                     /* pmenu->createEmptyMenu( clickedPoint() );
0090                         pmenu->popup( QCursor::pos() );*/
0091                 }
0092                 //}
0093                 break;
0094             default:
0095                 ;
0096         }
0097     }
0098 }
0099 
0100 void SkyMapLite::mouseReleaseEvent(QMouseEvent *)
0101 {
0102     /*if ( ZoomRect.isValid() ) {
0103         stopTracking();
0104         SkyPoint newcenter = projector()->fromScreen( ZoomRect.center(), data->lst(), data->geo()->lat() );
0105         setFocus( &newcenter );
0106         setDestination( newcenter );
0107 
0108         //Zoom in on center of Zoom Circle, by a factor equal to the ratio
0109         //of the sky pixmap's width to the Zoom Circle's diameter
0110         float factor = float(width()) / float(ZoomRect.width());
0111         setZoomFactor( Options::zoomFactor() * factor );
0112     }*/
0113     setDefaultMouseCursor();
0114     ZoomRect = QRect(); //invalidate ZoomRect
0115 
0116     /*if(m_previewLegend) {
0117         slotCancelLegendPreviewMode();
0118     }*/
0119 
0120     //false if double-clicked, because it's unset there.
0121     if (mouseButtonDown)
0122     {
0123         mouseButtonDown = false;
0124         if (getSlewing())
0125         {
0126             setSlewing(false);
0127             if (Options::useAltAz())
0128                 setDestinationAltAz(focus()->alt(), focus()->az(), false);
0129             else
0130                 setDestination(*focus());
0131         }
0132         forceUpdate(); // is needed because after moving the sky not all stars are shown
0133     }
0134     // if middle button was pressed unset here
0135     midMouseButtonDown = false;
0136 }
0137 
0138 void SkyMapLite::mouseDoubleClickEvent(QMouseEvent *e)
0139 {
0140     if (e->button() == Qt::LeftButton && !projector()->unusablePoint(e->pos()))
0141     {
0142         mouseButtonDown = false;
0143         if (e->x() != width() / 2 || e->y() != height() / 2)
0144             slotCenter();
0145     }
0146 }
0147 
0148 void SkyMapLite::mouseMoveEvent(QMouseEvent *e)
0149 {
0150     if (Options::useHoverLabel())
0151     {
0152         //Start a single-shot timer to monitor whether we are currently hovering.
0153         //The idea is that whenever a moveEvent occurs, the timer is reset.  It
0154         //will only timeout if there are no move events for HOVER_INTERVAL ms
0155         m_HoverTimer.start(HOVER_INTERVAL);
0156         //DELETE? QToolTip::hideText();
0157     }
0158 
0159     //Are we defining a ZoomRect?
0160     /*if ( ZoomRect.center().x() > 0 && ZoomRect.center().y() > 0 ) {
0161         //cancel operation if the user let go of CTRL
0162         if ( !( e->modifiers() & Qt::ControlModifier ) ) {
0163             ZoomRect = QRect(); //invalidate ZoomRect
0164             update();
0165         } else {
0166             //Resize the rectangle so that it passes through the cursor position
0167             QPoint pcenter = ZoomRect.center();
0168             int dx = abs(e->x() - pcenter.x());
0169             int dy = abs(e->y() - pcenter.y());
0170             if ( dx == 0 || float(dy)/float(dx) > float(height())/float(width()) ) {
0171                 //Size rect by height
0172                 ZoomRect.setHeight( 2*dy );
0173                 ZoomRect.setWidth( 2*dy*width()/height() );
0174             } else {
0175                 //Size rect by height
0176                 ZoomRect.setWidth( 2*dx );
0177                 ZoomRect.setHeight( 2*dx*height()/width() );
0178             }
0179             ZoomRect.moveCenter( pcenter ); //reset center
0180 
0181             update();
0182             return;
0183         }
0184     }
0185     */
0186     if (projector()->unusablePoint(e->pos()))
0187         return; // break if point is unusable
0188 
0189     //determine RA, Dec of mouse pointer
0190     m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat());
0191     double dyPix = 0.5 * height() - e->y();
0192     if (midMouseButtonDown) //zoom according to y-offset
0193     {
0194         float yoff = dyPix - y0;
0195         if (yoff > 10)
0196         {
0197             y0 = dyPix;
0198             slotZoomIn();
0199         }
0200         if (yoff < -10)
0201         {
0202             y0 = dyPix;
0203             slotZoomOut();
0204         }
0205     }
0206     if (mouseButtonDown)
0207     {
0208         // set the mouseMoveCursor and set slewing to true, if they are not set yet
0209         if (!mouseMoveCursor)
0210             setMouseMoveCursor();
0211         if (!getSlewing())
0212         {
0213             setSlewing(true);
0214             stopTracking(); //toggle tracking off
0215         }
0216 
0217         //Update focus such that the sky coords at mouse cursor remain approximately constant
0218         if (Options::useAltAz())
0219         {
0220             m_MousePoint.EquatorialToHorizontal(data->lst(), data->geo()->lat());
0221             clickedPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0222             dms dAz  = m_MousePoint.az() - clickedPoint()->az();
0223             dms dAlt = m_MousePoint.altRefracted() - clickedPoint()->altRefracted();
0224             focus()->setAz(focus()->az().Degrees() - dAz.Degrees()); //move focus in opposite direction
0225             focus()->setAz(focus()->az().reduce());
0226             focus()->setAltRefracted(KSUtils::clamp(focus()->altRefracted().Degrees() - dAlt.Degrees(), -90.0, 90.0));
0227             focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0228         }
0229         else
0230         {
0231             dms dRA  = m_MousePoint.ra() - clickedPoint()->ra();
0232             dms dDec = m_MousePoint.dec() - clickedPoint()->dec();
0233             focus()->setRA(focus()->ra().Hours() - dRA.Hours()); //move focus in opposite direction
0234             focus()->setRA(focus()->ra().reduce());
0235             focus()->setDec(KSUtils::clamp(focus()->dec().Degrees() - dDec.Degrees(), -90.0, 90.0));
0236             focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0237         }
0238         //showFocusCoords();
0239 
0240         //redetermine RA, Dec of mouse pointer, using new focus
0241         m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat());
0242         setClickedPoint(&m_MousePoint);
0243 
0244         forceUpdate(); // must be new computed
0245     }
0246     else //mouse button not down
0247     {
0248         if (Options::useAltAz())
0249             m_MousePoint.EquatorialToHorizontal(data->lst(), data->geo()->lat());
0250         emit mousePointChanged(&m_MousePoint);
0251     }
0252 }
0253 
0254 void SkyMapLite::wheelEvent(QWheelEvent *e)
0255 {
0256     if (e->delta() > 0)
0257     {
0258         zoomInOrMagStep(e->modifiers());
0259         //setRotation(rotation() + 0.1); TEST
0260     }
0261     else if (e->delta() < 0)
0262     {
0263         zoomOutOrMagStep(e->modifiers());
0264         //setRotation(rotation() - 0.1); TEST
0265     }
0266 }
0267 
0268 void SkyMapLite::touchEvent(QTouchEvent *e)
0269 {
0270     QList<QTouchEvent::TouchPoint> points = e->touchPoints();
0271 
0272     bool autoMode = false; //Always false for devices that doesn't support automatic mode
0273 #if defined(Q_OS_ANDROID)
0274     autoMode = getAutomaticMode();
0275 #endif
0276 
0277     if (points.length() == 2)
0278     {
0279         //Set tapBegan to false because user doesn't tap but either pans or pinches to zoom
0280         tapBegan = false;
0281         if (projector()->unusablePoint(points[0].pos()) || projector()->unusablePoint(points[1].pos()))
0282             return;
0283 
0284         //Pinch to zoom
0285         double x_old_diff = qFabs(points[1].lastPos().x() - points[0].lastPos().x());
0286         double y_old_diff = qFabs(points[1].lastPos().y() - points[0].lastPos().y());
0287 
0288         //Manhattan distance of old points
0289         double md_old = x_old_diff + y_old_diff;
0290 
0291         double x_diff = qFabs(points[1].pos().x() - points[0].pos().x());
0292         double y_diff = qFabs(points[1].pos().y() - points[0].pos().y());
0293 
0294         //Manhattan distance of new points
0295         double md_new = x_diff + y_diff;
0296 
0297         int zoomThreshold = 5;
0298 
0299         if (md_old - md_new < -zoomThreshold)
0300             zoomInOrMagStep(Qt::MetaModifier);
0301         else if (md_old - md_new > zoomThreshold)
0302             zoomOutOrMagStep(Qt::MetaModifier);
0303 
0304         double x_min = points[1].pos().x() < points[0].pos().x() ? points[1].pos().x() : points[0].pos().x();
0305         double y_min = points[1].pos().y() < points[0].pos().y() ? points[1].pos().y() : points[0].pos().y();
0306 
0307         //Center point on the line between 2 touch points used for moveEvent
0308         QPointF pinchCenter(x_min + x_diff / 2, y_min + y_diff / 2);
0309 
0310         //Pinch(turn) to rotate
0311         QPointF old_vec = points[1].lastPos() - points[0].lastPos();
0312         QPointF new_vec = points[1].pos() - points[0].pos();
0313 
0314         double old_vec_len = sqrt((old_vec.x() * old_vec.x()) + (old_vec.y() * old_vec.y()));
0315         double new_vec_len = sqrt((new_vec.x() * new_vec.x()) + (new_vec.y() * new_vec.y()));
0316 
0317         //Normalize vectors
0318         old_vec = QPointF(old_vec.x() / old_vec_len, old_vec.y() / old_vec_len);
0319         new_vec = QPointF(new_vec.x() / new_vec_len, new_vec.y() / new_vec_len);
0320 
0321         double old_atan = qAtan2(old_vec.y(), old_vec.x());
0322         double new_atan = qAtan2(new_vec.y(), new_vec.x());
0323 
0324         /*Workaround to solve the strange bug when sometimes signs of qAtan2 results
0325         for 2 vectors are different*/
0326         if ((new_atan > 0 && old_atan < 0) || (new_atan < 0 && old_atan > 0))
0327         {
0328             old_atan *= -1;
0329         }
0330 
0331         //Get the angle between two vectors
0332         /*double delta = new_atan - old_atan;
0333 
0334         //Rotation is under construction
0335         if(rotation() > 360) {
0336             setRotation(0);
0337         } else if(rotation() < 0) {
0338             setRotation(360);
0339         }
0340 
0341         //Scale the angle to speed up the rotation
0342         delta *= 100;
0343         setRotation(rotation() + delta);
0344         //update(); //Apply rotation*/
0345 
0346         //Allow movement of SkyMapLite while rotating or zooming
0347         if (!getCenterLocked() && !autoMode)
0348         {
0349             QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, pinchCenter, Qt::LeftButton, Qt::LeftButton,
0350                                                  Qt::ControlModifier);
0351             if (!pinch)
0352             {
0353                 m_MousePoint = projector()->fromScreen(pinchCenter, data->lst(), data->geo()->lat());
0354                 setClickedPoint(&m_MousePoint);
0355                 mouseButtonDown = true;
0356                 pinch           = true;
0357             }
0358             mouseMoveEvent(event);
0359             delete event;
0360         }
0361 
0362         if (e->touchPointStates() & Qt::TouchPointReleased)
0363         {
0364             setSlewing(false);
0365             if (pinch)
0366             {
0367                 pinch           = false;
0368                 mouseButtonDown = false;
0369             }
0370         }
0371     }
0372     else if (points.length() == 1 && !pinch && !autoMode)
0373     {
0374         QPointF point = points[0].screenPos();
0375         //Set clicked point (needed for pan)
0376         if (e->type() == QEvent::TouchBegin)
0377         {
0378             m_MousePoint = projector()->fromScreen(point, data->lst(), data->geo()->lat());
0379             setClickedPoint(&m_MousePoint);
0380             mouseButtonDown = true;
0381         }
0382         else if (e->type() == QEvent::TouchEnd)
0383         {
0384             mouseButtonDown = false;
0385             if (getSlewing())
0386             {
0387                 setSlewing(false);
0388                 if (Options::useAltAz())
0389                     setDestinationAltAz(focus()->alt(), focus()->az(), false);
0390                 else
0391                     setDestination(*focus());
0392             }
0393         }
0394 
0395         if (!projector()->unusablePoint(points[0].screenPos()))
0396         {
0397             if (!tapBegan && (e->touchPointStates() & Qt::TouchPointPressed))
0398             {
0399                 //We set tapBegan to true whenever user tapped on the screen
0400                 tapBegan = true;
0401                 m_tapBeganTimer.start(100);
0402             }
0403             else if ((e->touchPointStates() & Qt::TouchPointMoved) || getSlewing())
0404             {
0405                 //Set tapBegan to false because user doesn't tap but either pans or pinches to zoom
0406                 if (m_tapBeganTimer.remainingTime() > 0)
0407                 {
0408                     return;
0409                 }
0410                 else
0411                 {
0412                     m_tapBeganTimer.stop();
0413                 }
0414                 tapBegan = false;
0415 
0416                 QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton, Qt::LeftButton,
0417                                                      Qt::ControlModifier);
0418                 mouseMoveEvent(event);
0419                 delete event;
0420 
0421                 //If user didn't pan and pinch to zoom tapBegan will be true
0422             }
0423             else if ((e->touchPointStates() & Qt::TouchPointReleased) && tapBegan)
0424             {
0425                 if (getSlewing())
0426                     setSlewing(false);
0427                 tapBegan = false;
0428                 //Show tap animation
0429                 emit posClicked(point);
0430                 //determine RA, Dec of touch
0431                 m_MousePoint = projector()->fromScreen(point, data->lst(), data->geo()->lat());
0432                 setClickedPoint(&m_MousePoint);
0433 
0434                 //Find object nearest to clickedPoint()
0435                 double maxrad =
0436                     1000.0 /
0437                     Options::
0438                     zoomFactor(); /* On high zoom-level it is very hard to select the object using touch screen.
0439                                             That's why radius remains constant*/
0440                 maxrad         = qMax(maxrad, 2.5);
0441                 SkyObject *obj = data->skyComposite()->objectNearest(clickedPoint(), maxrad);
0442                 setClickedObject(obj);
0443                 if (obj)
0444                     setClickedPoint(obj);
0445 
0446                 if (clickedObject())
0447                 {
0448                     emit objectLiteChanged();
0449                 }
0450                 else
0451                 {
0452                     emit pointLiteChanged();
0453                 }
0454             }
0455         }
0456     }
0457 }
0458 
0459 double SkyMapLite::zoomFactor(const int modifier)
0460 {
0461     double factor = (modifier & Qt::ControlModifier) ? DZOOM : 2.0;
0462     if (modifier & Qt::ShiftModifier)
0463         factor = sqrt(factor);
0464     return factor;
0465 }
0466 
0467 void SkyMapLite::zoomInOrMagStep(const int modifier)
0468 {
0469     if (modifier & Qt::AltModifier)
0470         incMagLimit(modifier);
0471     else if (modifier & Qt::MetaModifier) //Used in pinch-to-zoom gesture
0472     {
0473         setZoomFactor(Options::zoomFactor() + Options::zoomFactor() * 0.05);
0474     }
0475     else
0476         setZoomFactor(Options::zoomFactor() * zoomFactor(modifier));
0477 }
0478 
0479 void SkyMapLite::zoomOutOrMagStep(const int modifier)
0480 {
0481     if (modifier & Qt::AltModifier)
0482         decMagLimit(modifier);
0483     else if (modifier & Qt::MetaModifier) //Used in pinch-to-zoom gesture
0484     {
0485         setZoomFactor(Options::zoomFactor() - Options::zoomFactor() * 0.05);
0486     }
0487     else
0488         setZoomFactor(Options::zoomFactor() / zoomFactor(modifier));
0489 }
0490 
0491 double SkyMapLite::magFactor(const int modifier)
0492 {
0493     double factor = (modifier & Qt::ControlModifier) ? 0.1 : 0.5;
0494     if (modifier & Qt::ShiftModifier)
0495         factor *= 2.0;
0496     return factor;
0497 }
0498 
0499 void SkyMapLite::setMagLim(double magLim)
0500 {
0501     if (m_magLim != magLim)
0502     {
0503         m_magLim = magLim;
0504         if (m_magLim > 5.75954)
0505             m_magLim = 5.75954;
0506         if (m_magLim < 1.18778)
0507             m_magLim = 1.18778;
0508         emit magLimChanged(m_magLim);
0509 
0510         Options::setStarDensity(pow(10, (m_magLim - 0.35) / 2.222));
0511         //printf("maglim set to %3.1f\n", m_magLim);
0512         forceUpdate();
0513     }
0514 }
0515 
0516 void SkyMapLite::stopTracking()
0517 {
0518     KStarsLite *kstars = KStarsLite::Instance();
0519     emit positionChanged();
0520     if (kstars && Options::isTracking())
0521         kstars->slotTrack();
0522 }
0523 
0524 uint SkyMapLite::projType() const
0525 {
0526     return m_proj->type();
0527 }
0528 
0529 void SkyMapLite::incMagLimit(const int modifier)
0530 {
0531     m_magLim = 2.222 * log10(static_cast<double>(Options::starDensity())) + 0.35;
0532     m_magLim += magFactor(modifier);
0533     if (m_magLim > 5.75954)
0534         m_magLim = 5.75954;
0535     emit magLimChanged(m_magLim);
0536     Options::setStarDensity(pow(10, (m_magLim - 0.35) / 2.222));
0537     //printf("maglim set to %3.1f\n", m_magLim);
0538     forceUpdate();
0539 }
0540 
0541 void SkyMapLite::decMagLimit(const int modifier)
0542 {
0543     m_magLim = 2.222 * log10(static_cast<double>(Options::starDensity())) + 0.35;
0544     m_magLim -= magFactor(modifier);
0545     if (m_magLim < 1.18778)
0546         m_magLim = 1.18778;
0547     emit magLimChanged(m_magLim);
0548     Options::setStarDensity(pow(10, (m_magLim - 0.35) / 2.222));
0549     //printf("maglim set to %3.1f\n", m_magLim);
0550     forceUpdate();
0551 }