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 }