File indexing completed on 2024-03-24 15:16:55

0001 /*
0002     SPDX-FileCopyrightText: 2003-2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003     SPDX-FileCopyrightText: 2016-2017 Robert Lancaster <rlancaste@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "fitslabel.h"
0009 
0010 #include "config-kstars.h"
0011 
0012 #include "fitsdata.h"
0013 #include "fitsview.h"
0014 #include "kspopupmenu.h"
0015 #include "kstars.h"
0016 #include "kstarsdata.h"
0017 #include "Options.h"
0018 #include "skymap.h"
0019 #include "ksnotification.h"
0020 #include <QGuiApplication>
0021 
0022 #ifdef HAVE_INDI
0023 #include "basedevice.h"
0024 #include "indi/indilistener.h"
0025 #include "indi/indiconcretedevice.h"
0026 #include "indi/indimount.h"
0027 #endif
0028 
0029 #include <QScrollBar>
0030 #include <QToolTip>
0031 
0032 #define BASE_OFFSET    50
0033 #define ZOOM_DEFAULT   100.0
0034 #define ZOOM_MIN       10
0035 #define ZOOM_MAX       400
0036 #define ZOOM_LOW_INCR  10
0037 #define ZOOM_HIGH_INCR 50
0038 
0039 FITSLabel::FITSLabel(FITSView *View, QWidget *parent) : QLabel(parent)
0040 {
0041     this->view = View;
0042     prevscale = 0.0;
0043     //Rubber Band options
0044     roiRB = new QRubberBand( QRubberBand::Rectangle, this);
0045     roiRB->setAttribute(Qt::WA_TransparentForMouseEvents, 1);
0046     roiRB->setGeometry(QRect(QPoint(1, 1), QPoint(100, 100)));
0047 
0048     QPalette pal;
0049     QColor red70 = Qt::red;
0050     red70.setAlphaF(0.7);
0051 
0052     pal.setBrush(QPalette::Highlight, QBrush(red70));
0053     roiRB->setPalette(pal);
0054     QToolTip::showText(QPoint(1, 1), "Move Once to show selection stats", this);
0055 }
0056 
0057 
0058 void FITSLabel::setSize(double w, double h)
0059 {
0060     m_Width  = w;
0061     m_Height = h;
0062     m_Size   = w * h;
0063 }
0064 
0065 bool FITSLabel::getMouseButtonDown()
0066 {
0067     return mouseButtonDown;
0068 }
0069 
0070 /**
0071 This method was added to make the panning function work.
0072 If the mouse button is released, it resets mouseButtonDown variable and the mouse cursor.
0073  */
0074 void FITSLabel::mouseReleaseEvent(QMouseEvent *e)
0075 {
0076     float scale = (view->getCurrentZoom() / ZOOM_DEFAULT);
0077 
0078     double x = round(e->x() / scale);
0079     double y = round(e->y() / scale);
0080 
0081     m_p2.setX(x);//record second point for selection Rectangle
0082     m_p2.setY(y);
0083 
0084     if (view->getCursorMode() == FITSView::dragCursor)
0085     {
0086         mouseButtonDown = false;
0087         view->updateMouseCursor();
0088         if( isRoiSelected && view->isSelectionRectShown())
0089         {
0090             QRect roiRaw = roiRB->geometry();
0091             emit rectangleSelected(roiRaw.topLeft() / prevscale, roiRaw.bottomRight() / prevscale, true);
0092             updateROIToolTip(e->globalPos());
0093         }
0094         if( e->modifiers () == Qt::ShiftModifier && view->isSelectionRectShown())
0095         {
0096             QRect roiRaw = roiRB->geometry();
0097             emit rectangleSelected(roiRaw.topLeft() / prevscale, roiRaw.bottomRight() / prevscale, true);
0098             updateROIToolTip(e->globalPos());
0099         }
0100         isRoiSelected = false;
0101     }
0102 
0103 }
0104 
0105 void FITSLabel::leaveEvent(QEvent *e)
0106 {
0107     Q_UNUSED(e)
0108     view->updateMagnifyingGlass(-1, -1);
0109     emit mouseOverPixel(-1, -1);
0110 }
0111 
0112 /**
0113 I added some things to the top of this method to allow panning and Scope slewing to function.
0114 If you are in the dragMouse mode and the mousebutton is pressed, The method checks the difference
0115 between the location of the last point stored and the current event point to see how the mouse has moved.
0116 Then it moves the scrollbars and thus the view to the right location.
0117 Then it stores the current point so next time it can do it again.
0118  */
0119 void FITSLabel::mouseMoveEvent(QMouseEvent *e)
0120 {
0121     const QSharedPointer<FITSData> &imageData = view->imageData();
0122     if (imageData.isNull())
0123         return;
0124 
0125     float scale = (view->getCurrentZoom() / ZOOM_DEFAULT);
0126 
0127     double x = round(e->x() / scale);
0128     double y = round(e->y() / scale);
0129 
0130     //Panning
0131     if (e->modifiers() != Qt::ShiftModifier  && view->getCursorMode() == FITSView::dragCursor && mouseButtonDown  )
0132     {
0133         QPoint newPoint = e->globalPos();
0134         int dx          = newPoint.x() - lastMousePoint.x();
0135         int dy          = newPoint.y() - lastMousePoint.y();
0136         view->horizontalScrollBar()->setValue(view->horizontalScrollBar()->value() - dx);
0137         view->verticalScrollBar()->setValue(view->verticalScrollBar()->value() - dy);
0138 
0139         lastMousePoint = newPoint;
0140     }
0141     if( e->buttons() & Qt::LeftButton && view->getCursorMode() == FITSView::dragCursor )
0142     {
0143         //Translation of ROI
0144         if(isRoiSelected && !mouseButtonDown)
0145         {
0146 
0147             int xdiff = x - prevPoint.x();
0148             int ydiff = y - prevPoint.y();
0149             roiRB->setGeometry(roiRB->geometry().translated(round(xdiff * scale), round(ydiff * scale)));
0150             prevPoint = QPoint(x, y);
0151 
0152             QRect roiRaw = roiRB->geometry();
0153             // Opting to update stats on the go is extremely laggy for large images, only update if small image
0154             if(!view->isLargeImage())
0155             {
0156                 emit rectangleSelected(roiRaw.topLeft() / prevscale, roiRaw.bottomRight() / prevscale, true);
0157                 updateROIToolTip(e->globalPos());
0158             }
0159         }
0160         //Stretching of ROI
0161         if(e->modifiers() == Qt::ShiftModifier && !isRoiSelected && view->isSelectionRectShown())
0162         {
0163             roiRB->setGeometry(QRect(m_p1 * scale, QPoint(x, y)*scale).normalized());
0164 
0165             QRect roiRaw = roiRB->geometry();
0166             // Opting to update stats on the go is extremely laggy for large images, only update if small image
0167             if(!view->isLargeImage())
0168             {
0169                 emit rectangleSelected(roiRaw.topLeft() / prevscale, roiRaw.bottomRight() / prevscale, true);
0170                 updateROIToolTip(e->globalPos());
0171             }
0172         }
0173     }
0174 
0175     uint8_t const *buffer = imageData->getImageBuffer();
0176 
0177     if (buffer == nullptr)
0178         return;
0179 
0180     x = round(e->x() / scale);
0181     y = round(e->y() / scale);
0182 
0183     if(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier))
0184         view->updateMagnifyingGlass(x, y);
0185     else
0186         view->updateMagnifyingGlass(-1, -1);
0187 
0188     x = KSUtils::clamp(x, 1.0, m_Width);
0189     y = KSUtils::clamp(y, 1.0, m_Height);
0190 
0191     emit newStatus(QString("X:%1 Y:%2").arg(static_cast<int>(x)).arg(static_cast<int>(y)), FITS_POSITION);
0192 
0193     // Range is 0 to dim-1 when accessing array
0194     x -= 1;
0195     y -= 1;
0196 
0197     emit mouseOverPixel(x, y);
0198 
0199     int index = y * m_Width + x;
0200     QString stringValue;
0201 
0202     switch (imageData->getStatistics().dataType)
0203     {
0204         case TBYTE:
0205             stringValue = QLocale().toString(buffer[index]);
0206             break;
0207 
0208         case TSHORT:
0209             stringValue = QLocale().toString((reinterpret_cast<int16_t const*>(buffer))[index]);
0210             break;
0211 
0212         case TUSHORT:
0213             stringValue = QLocale().toString((reinterpret_cast<uint16_t const*>(buffer))[index]);
0214             break;
0215 
0216         case TLONG:
0217             stringValue = QLocale().toString((reinterpret_cast<int32_t const*>(buffer))[index]);
0218             break;
0219 
0220         case TULONG:
0221             stringValue = QLocale().toString((reinterpret_cast<uint32_t const*>(buffer))[index]);
0222             break;
0223 
0224         case TFLOAT:
0225             stringValue = QLocale().toString((reinterpret_cast<float const*>(buffer))[index], 'f', 5);
0226             break;
0227 
0228         case TLONGLONG:
0229             stringValue = QLocale().toString(static_cast<int>((reinterpret_cast<int64_t const*>(buffer))[index]));
0230             break;
0231 
0232         case TDOUBLE:
0233             stringValue = QLocale().toString((reinterpret_cast<float const*>(buffer))[index], 'f', 5);
0234 
0235             break;
0236 
0237         default:
0238             break;
0239 
0240     }
0241 
0242     if(view->isSelectionRectShown())
0243     {
0244         if (roiRB->geometry().contains(e->pos()))
0245             updateROIToolTip(e->globalPos());
0246         else
0247             QToolTip::hideText();
0248     }
0249 
0250     emit newStatus(stringValue, FITS_VALUE);
0251 
0252     if (imageData->hasWCS() &&
0253             !view->isSelectionRectShown() &&
0254             view->getCursorMode() != FITSView::selectCursor)
0255     {
0256         QPointF wcsPixelPoint(x, y);
0257         SkyPoint wcsCoord;
0258         if(imageData->pixelToWCS(wcsPixelPoint, wcsCoord))
0259         {
0260             m_RA = wcsCoord.ra0();
0261             m_DE = wcsCoord.dec0();
0262             emit newStatus(QString("%1 , %2").arg(m_RA.toHMSString(), m_DE.toDMSString()), FITS_WCS);
0263         }
0264 
0265         bool objFound = false;
0266         for (auto &listObject : imageData->getSkyObjects())
0267         {
0268             if ((std::abs(listObject->x() - x) < 5 / scale) && (std::abs(listObject->y() - y) < 5 / scale))
0269             {
0270                 QToolTip::showText(e->globalPos(),
0271                                    QToolTip::text() + '\n' + listObject->skyObject()->name() + '\n' + listObject->skyObject()->longname(), this);
0272                 objFound = true;
0273                 break;
0274             }
0275         }
0276         if (!objFound && !view->isSelectionRectShown())
0277             QToolTip::hideText();
0278     }
0279 
0280     double HFR = view->imageData()->getHFR(x + 1, y + 1, scale);
0281 
0282 
0283     if (HFR > 0)
0284     {
0285         QString tip = QToolTip::text();
0286         // Don't i18n away HFR: because the RegExp below checks for HFR: to make sure there aren't duplicate strings added.
0287         QString hfrStr = QString("HFR: %1").arg(HFR, 4, 'f', 2);
0288         if (tip.isEmpty() || tip == hfrStr)
0289             QToolTip::showText(e->globalPos(), hfrStr, this);
0290         else
0291         {
0292             QRegExp hfrRegEx("HFR\\: \\d+\\.\\d\\d");
0293             if (tip.contains(hfrRegEx))
0294                 QToolTip::showText(e->globalPos(), tip.replace(hfrRegEx, hfrStr), this);
0295             else
0296                 QToolTip::showText(e->globalPos(), QToolTip::text() + '\n' + hfrStr, this);
0297         }
0298     }
0299 
0300     e->accept();
0301 }
0302 
0303 /**
0304 I added some things to the top of this method to allow panning and Scope slewing to function.
0305 If in dragMouse mode, the Panning function works by storing the cursor position when the mouse was pressed and setting
0306 the mouseButtonDown variable to true.
0307 If in ScopeMouse mode and the mouse is clicked, if there is WCS data and a scope is available, the method will verify that you actually
0308 do want to slew to the WCS coordinates associated with the click location.  If so, it calls the centerTelescope function.
0309  */
0310 
0311 void FITSLabel::mousePressEvent(QMouseEvent *e)
0312 {
0313     float scale = (view->getCurrentZoom() / ZOOM_DEFAULT);
0314 
0315     double x = round(e->x() / scale);
0316     double y = round(e->y() / scale);
0317 
0318     m_p1.setX(x);//record first point for selection Rectangle
0319     m_p1.setY(y);
0320     prevPoint = QPoint(x, y);
0321     prevscale = scale;
0322     x = KSUtils::clamp(x, 1.0, m_Width);
0323     y = KSUtils::clamp(y, 1.0, m_Height);
0324 
0325     if(e->buttons() & Qt::LeftButton && view->getCursorMode() == FITSView::dragCursor)
0326     {
0327         if(roiRB->geometry().contains(x * scale, y * scale))
0328             isRoiSelected = true;
0329     }
0330 
0331     if (view->getCursorMode() == FITSView::dragCursor && !isRoiSelected)
0332     {
0333         mouseButtonDown = true;
0334         lastMousePoint  = e->globalPos();
0335         view->updateMouseCursor();
0336     }
0337     else if (e->buttons() & Qt::LeftButton && view->getCursorMode() == FITSView::scopeCursor)
0338     {
0339 #ifdef HAVE_INDI
0340         const QSharedPointer<FITSData> &view_data = view->imageData();
0341         if (view_data->hasWCS())
0342         {
0343             QPointF wcsPixelPoint(x, y);
0344             SkyPoint wcsCoord;
0345             if(view_data->pixelToWCS(wcsPixelPoint, wcsCoord))
0346             {
0347                 auto ra = wcsCoord.ra0();
0348                 auto dec = wcsCoord.dec0();
0349                 if (KMessageBox::Continue == KMessageBox::warningContinueCancel(
0350                             nullptr,
0351                             "Slewing to Coordinates: \nRA: " + ra.toHMSString() +
0352                             "\nDec: " + dec.toDMSString(),
0353                             i18n("Continue Slew"), KStandardGuiItem::cont(),
0354                             KStandardGuiItem::cancel(), "continue_slew_warning"))
0355                 {
0356                     centerTelescope(ra.Hours(), dec.Degrees());
0357                     view->setCursorMode(view->lastMouseMode);
0358                     view->updateScopeButton();
0359                 }
0360             }
0361         }
0362 #endif
0363     }
0364 
0365 
0366 #ifdef HAVE_INDI
0367     const QSharedPointer<FITSData> &view_data = view->imageData();
0368 
0369     if (e->buttons() & Qt::RightButton && view->getCursorMode() != FITSView::scopeCursor)
0370     {
0371         mouseReleaseEvent(e);
0372         if (view_data->hasWCS())
0373         {
0374             for (auto &listObject : view_data->getSkyObjects())
0375             {
0376                 if ((std::abs(listObject->x() - x) < 10 / scale) && (std::abs(listObject->y() - y) < 10 / scale))
0377                 {
0378                     SkyObject *object = listObject->skyObject();
0379                     KSPopupMenu *pmenu;
0380                     pmenu = new KSPopupMenu();
0381                     object->initPopupMenu(pmenu);
0382                     QList<QAction *> actions = pmenu->actions();
0383                     for (auto action : actions)
0384                     {
0385                         if (action->text().left(7) == "Starhop")
0386                             pmenu->removeAction(action);
0387                         if (action->text().left(7) == "Angular")
0388                             pmenu->removeAction(action);
0389                         if (action->text().left(8) == "Add flag")
0390                             pmenu->removeAction(action);
0391                         if (action->text().left(12) == "Attach Label")
0392                             pmenu->removeAction(action);
0393                     }
0394                     pmenu->popup(e->globalPos());
0395                     KStars::Instance()->map()->setClickedObject(object);
0396                     break;
0397                 }
0398             }
0399         }
0400 
0401         if (fabs(view->markerCrosshair.x() - x) <= 15 && fabs(view->markerCrosshair.y() - y) <= 15)
0402             emit markerSelected(0, 0);
0403     }
0404 #endif
0405 
0406     if  (e->buttons() & Qt::LeftButton)
0407     {
0408         if (view->getCursorMode() == FITSView::selectCursor)
0409             emit pointSelected(x, y);
0410         else if (view->getCursorMode() == FITSView::crosshairCursor)
0411             emit pointSelected(x + 5 / scale, y + 5 / scale);
0412     }
0413 }
0414 
0415 void FITSLabel::mouseDoubleClickEvent(QMouseEvent *e)
0416 {
0417     double x, y;
0418 
0419     x = round(e->x() / (view->getCurrentZoom() / ZOOM_DEFAULT));
0420     y = round(e->y() / (view->getCurrentZoom() / ZOOM_DEFAULT));
0421 
0422     x = KSUtils::clamp(x, 1.0, m_Width);
0423     y = KSUtils::clamp(y, 1.0, m_Height);
0424 
0425     emit markerSelected(x, y);
0426 
0427     return;
0428 }
0429 
0430 void FITSLabel::centerTelescope(double raJ2000, double decJ2000)
0431 {
0432 #ifdef HAVE_INDI
0433 
0434     if (INDIListener::Instance()->size() == 0)
0435     {
0436         KSNotification::sorry(i18n("KStars did not find any active mounts."));
0437         return;
0438     }
0439 
0440     for (auto &oneDevice : INDIListener::devices())
0441     {
0442         if (!(oneDevice->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE))
0443             continue;
0444 
0445         if (oneDevice->isConnected() == false)
0446         {
0447             KSNotification::error(i18n("Mount %1 is offline. Please connect and retry again.", oneDevice->getDeviceName()));
0448             return;
0449         }
0450 
0451         auto mount = oneDevice->getMount();
0452         if (!mount)
0453             continue;
0454 
0455         SkyPoint selectedObject;
0456         selectedObject.setRA0(raJ2000);
0457         selectedObject.setDec0(decJ2000);
0458         selectedObject.apparentCoord(J2000, KStarsData::Instance()->ut().djd());
0459         mount->Slew(&selectedObject);
0460         return;
0461     }
0462 
0463     KSNotification::sorry(i18n("KStars did not find any active mounts."));
0464 
0465 #else
0466 
0467     Q_UNUSED(raJ2000);
0468     Q_UNUSED(decJ2000);
0469 
0470 #endif
0471 }
0472 
0473 void FITSLabel::showRubberBand(bool on)
0474 {
0475     if(on)
0476     {
0477         roiRB->show();
0478     }
0479     else
0480     {
0481         roiRB->hide();
0482     }
0483 }
0484 
0485 /// Scales the rubberband on zoom
0486 void FITSLabel::zoomRubberBand(double scale)
0487 {
0488     QRect r = roiRB->geometry() ;
0489 
0490     if(prevscale == 0.0 )
0491         prevscale = scale;
0492 
0493     double ap = r.width() / r.height();
0494     double ow = r.width() * scale / prevscale;
0495     double oh = r.height() * scale / prevscale;
0496 
0497     int rx, ry;
0498     rx = round(r.topLeft().x() * scale / prevscale);
0499     ry = round(r.topLeft().y() * scale / prevscale);
0500     r.setTopLeft(QPoint(rx, ry));
0501 
0502     rx = round(r.bottomRight().x() * scale / prevscale);
0503     ry = round(r.bottomRight().y() * scale / prevscale);
0504     r.setBottomRight(QPoint(rx, ry));
0505 
0506     if (ap != r.width() / r.height())
0507     {
0508         r.setSize(QSize(ow, oh));
0509     }
0510 
0511     roiRB->setGeometry(r);
0512     prevscale = scale;
0513 }
0514 /// Intended to take raw rect as input from FITSView context
0515 void FITSLabel::setRubberBand(QRect rect)
0516 {
0517     float scale = (view->getCurrentZoom() / ZOOM_DEFAULT);
0518 
0519     int rx, ry;
0520     rx = round(rect.topLeft().x() * scale );
0521     ry = round(rect.topLeft().y() * scale );
0522     rect.setTopLeft(QPoint(rx, ry));
0523 
0524     rx = round(rect.bottomRight().x() * scale );
0525     ry = round(rect.bottomRight().y() * scale );
0526     rect.setBottomRight(QPoint(rx, ry));
0527 
0528     roiRB->setGeometry(rect);
0529     prevscale = scale;
0530 }
0531 
0532 void FITSLabel::updateROIToolTip(const QPoint p)
0533 {
0534     auto result = QString("σ %1").arg(QString::number(view->imageData()->getAverageStdDev(true), 'f', 2));
0535     result += "\nx̄ " + QString::number(view->imageData()->getAverageMean(true), 'f', 2);
0536     result += "\nM " + QString::number(view->imageData()->getAverageMedian(true), 'f', 2);
0537     QToolTip::showText(p, result, this);
0538 }